@nextclaw/service 0.1.9 → 0.1.11

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 (23) hide show
  1. package/dist/cli/commands/agent/cli-agent-runner.utils.d.ts +0 -5
  2. package/dist/commands/channel/channel-list-view.service.d.ts +33 -0
  3. package/dist/commands/channel/channel-list-view.service.js +81 -0
  4. package/dist/commands/channel/index.d.ts +4 -1
  5. package/dist/commands/channel/index.js +24 -0
  6. package/dist/commands/plugin/plugin-registry-loader.utils.d.ts +1 -2
  7. package/dist/commands/service/services/autostart/windows-task-autostart.service.js +2 -1
  8. package/dist/index.d.ts +2 -2
  9. package/dist/launcher/npm-runtime-launcher.service.js +3 -2
  10. package/dist/service-runtime.service.js +21 -27
  11. package/dist/shared/services/extensions/extension-lifecycle.service.d.ts +7 -0
  12. package/dist/shared/services/extensions/extension-lifecycle.service.js +34 -3
  13. package/dist/shared/services/extensions/service-extension-runtime.service.d.ts +1 -0
  14. package/dist/shared/services/extensions/service-extension-runtime.service.js +25 -39
  15. package/dist/shared/services/gateway/nextclaw-gateway-runtime.service.js +1 -1
  16. package/dist/shared/services/runtime/runtime-command.service.js +3 -2
  17. package/dist/shared/services/runtime/service-managed-startup.service.js +3 -2
  18. package/dist/shared/services/session/service-deferred-ncp-agent.service.js +1 -1
  19. package/dist/shared/services/ui/companion-runtime.service.js +1 -0
  20. package/dist/shared/services/workspace/workspace-manager.service.js +1 -1
  21. package/dist/shared/types/cli.types.d.ts +4 -1
  22. package/dist/shared/utils/cli.utils.js +2 -1
  23. package/package.json +19 -19
@@ -11,11 +11,6 @@ declare function runCliAgentCommand(params: {
11
11
  kernel: NextclawKernel;
12
12
  providerManager: LlmProviderRuntime;
13
13
  extensionRegistry: NextclawExtensionRegistry;
14
- loadResolvedConfig: () => Config;
15
- resolveMessageToolHints: (params: {
16
- channel: string;
17
- accountId?: string | null;
18
- }) => string[];
19
14
  }): Promise<void>;
20
15
  //#endregion
21
16
  export { runCliAgentCommand };
@@ -0,0 +1,33 @@
1
+ import { Config } from "@nextclaw/core";
2
+ import { PluginChannelBinding } from "@nextclaw/openclaw-compat";
3
+
4
+ //#region src/commands/channel/channel-list-view.service.d.ts
5
+ type ChannelListEntry = {
6
+ id: string;
7
+ enabled: boolean;
8
+ defaultAccountId?: string;
9
+ accounts?: ChannelListAccount[];
10
+ };
11
+ type ChannelListAccount = {
12
+ id: string;
13
+ userId?: string;
14
+ };
15
+ type ChannelListOutput = {
16
+ channels: ChannelListEntry[];
17
+ };
18
+ declare class ChannelListViewService {
19
+ build: (params: {
20
+ config: Config;
21
+ workspaceDir: string;
22
+ pluginBindings: PluginChannelBinding[];
23
+ }) => ChannelListOutput;
24
+ private toManifestChannelSources;
25
+ private toPluginChannelSource;
26
+ private toChannelListEntry;
27
+ private resolveAccounts;
28
+ private mergeChannelSources;
29
+ private discoverExtensionManifests;
30
+ private resolveDefaultAccountId;
31
+ }
32
+ //#endregion
33
+ export { ChannelListAccount, ChannelListEntry, ChannelListOutput, ChannelListViewService };
@@ -0,0 +1,81 @@
1
+ import { resolveChannelConfigView } from "./channel-config-view.js";
2
+ import { ExtensionManifestDiscoveryService } from "../../shared/services/extensions/extension-lifecycle.service.js";
3
+ import { resolveExtensionManifestRoots } from "../../shared/services/extensions/service-extension-runtime.service.js";
4
+ //#region src/commands/channel/channel-list-view.service.ts
5
+ function readRecord(value) {
6
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
7
+ return value;
8
+ }
9
+ function readString(value) {
10
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
11
+ }
12
+ var ChannelListViewService = class {
13
+ build = (params) => {
14
+ const { config, pluginBindings, workspaceDir } = params;
15
+ const sources = this.mergeChannelSources(pluginBindings.map(this.toPluginChannelSource), this.toManifestChannelSources(this.discoverExtensionManifests(config, workspaceDir)));
16
+ const channelConfigs = readRecord(resolveChannelConfigView(config, pluginBindings).channels) ?? {};
17
+ return { channels: sources.map((source) => this.toChannelListEntry(source, channelConfigs[source.id])).sort((left, right) => left.id.localeCompare(right.id)) };
18
+ };
19
+ toManifestChannelSources = (manifests) => {
20
+ const sources = [];
21
+ for (const manifest of manifests) {
22
+ const channels = manifest.contributes?.channels ?? [];
23
+ for (const channel of channels) {
24
+ const channelId = readString(channel.id);
25
+ if (!channelId) continue;
26
+ sources.push({ id: channelId });
27
+ }
28
+ }
29
+ return sources;
30
+ };
31
+ toPluginChannelSource = (binding) => ({
32
+ id: binding.channelId,
33
+ resolveDefaultAccountId: (channelConfig) => this.resolveDefaultAccountId(binding, channelConfig)
34
+ });
35
+ toChannelListEntry = (source, rawChannelConfig) => {
36
+ const channelConfig = readRecord(rawChannelConfig);
37
+ const defaultAccountId = readString(channelConfig?.defaultAccountId) ?? source.resolveDefaultAccountId?.(channelConfig);
38
+ const accounts = this.resolveAccounts(channelConfig);
39
+ return {
40
+ id: source.id,
41
+ enabled: channelConfig?.enabled === true,
42
+ ...defaultAccountId ? { defaultAccountId } : {},
43
+ ...accounts.length > 0 ? { accounts } : {}
44
+ };
45
+ };
46
+ resolveAccounts = (channelConfig) => {
47
+ const accounts = readRecord(channelConfig?.accounts);
48
+ if (!accounts) return [];
49
+ return Object.entries(accounts).map(([id, rawAccount]) => {
50
+ const userId = readString(readRecord(rawAccount)?.userId);
51
+ return {
52
+ id,
53
+ ...userId ? { userId } : {}
54
+ };
55
+ }).sort((left, right) => left.id.localeCompare(right.id));
56
+ };
57
+ mergeChannelSources = (pluginSources, extensionSources) => {
58
+ const sourcesByChannelId = /* @__PURE__ */ new Map();
59
+ for (const source of [...pluginSources, ...extensionSources]) sourcesByChannelId.set(source.id, source);
60
+ return [...sourcesByChannelId.values()];
61
+ };
62
+ discoverExtensionManifests = (config, workspaceDir) => {
63
+ const discovery = new ExtensionManifestDiscoveryService();
64
+ const roots = resolveExtensionManifestRoots({
65
+ config,
66
+ workspace: workspaceDir
67
+ });
68
+ return discovery.discoverSync(roots);
69
+ };
70
+ resolveDefaultAccountId = (binding, channelConfig) => {
71
+ const configAdapter = binding.channel.config?.defaultAccountId;
72
+ if (!configAdapter) return;
73
+ try {
74
+ return readString(configAdapter({ channels: { [binding.channelId]: channelConfig ?? {} } }));
75
+ } catch {
76
+ return;
77
+ }
78
+ };
79
+ };
80
+ //#endregion
81
+ export { ChannelListViewService };
@@ -1,18 +1,21 @@
1
- import { ChannelsAddOptions, ChannelsLoginOptions, RequestRestartParams } from "../../shared/types/cli.types.js";
1
+ import { ChannelsAddOptions, ChannelsListOptions, ChannelsLoginOptions, RequestRestartParams } from "../../shared/types/cli.types.js";
2
2
  import { resolveChannelConfigView } from "./channel-config-view.js";
3
3
 
4
4
  //#region src/commands/channel/index.d.ts
5
5
  declare class ChannelCommands {
6
6
  private deps;
7
+ private readonly channelListView;
7
8
  constructor(deps: {
8
9
  logo: string;
9
10
  getBridgeDir: () => string;
10
11
  requestRestart: (params: RequestRestartParams) => Promise<void>;
11
12
  });
12
13
  status: () => void;
14
+ list: (opts?: ChannelsListOptions) => void;
13
15
  login: (opts?: ChannelsLoginOptions) => Promise<void>;
14
16
  private runLegacyBridgeLogin;
15
17
  private resolvePluginChannelContext;
18
+ private buildChannelListOutput;
16
19
  private loginPluginChannel;
17
20
  private clonePluginConfig;
18
21
  private assertValidPluginLoginResult;
@@ -1,5 +1,6 @@
1
1
  import { loadPluginRegistry, mergePluginConfigView, toPluginConfigView as toPluginConfigView$1 } from "../plugin/index.js";
2
2
  import { resolveChannelConfigView } from "./channel-config-view.js";
3
+ import { ChannelListViewService } from "./channel-list-view.service.js";
3
4
  import { getWorkspacePath, loadConfig, saveConfig } from "@nextclaw/core";
4
5
  import { buildPluginStatusReport, enablePluginInConfig, getPluginChannelBindings } from "@nextclaw/openclaw-compat";
5
6
  import { spawnSync } from "node:child_process";
@@ -23,6 +24,7 @@ function resolveChannelBindings(pluginRegistry) {
23
24
  return getPluginChannelBindings(pluginRegistry);
24
25
  }
25
26
  var ChannelCommands = class {
27
+ channelListView = new ChannelListViewService();
26
28
  constructor(deps) {
27
29
  this.deps = deps;
28
30
  }
@@ -51,6 +53,18 @@ var ChannelCommands = class {
51
53
  }
52
54
  }
53
55
  };
56
+ list = (opts = {}) => {
57
+ const output = this.buildChannelListOutput();
58
+ if (opts.json) {
59
+ console.log(JSON.stringify(output, null, 2));
60
+ return;
61
+ }
62
+ console.log("Channels");
63
+ for (const channel of output.channels) {
64
+ const flags = [channel.enabled ? "enabled" : "disabled", channel.defaultAccountId ? `defaultAccountId=${channel.defaultAccountId}` : void 0].filter(Boolean);
65
+ console.log(`- ${channel.id} [${flags.join(", ")}]`);
66
+ }
67
+ };
54
68
  login = async (opts = {}) => {
55
69
  const channelId = opts.channel?.trim();
56
70
  if (!channelId) {
@@ -92,6 +106,16 @@ var ChannelCommands = class {
92
106
  bindings
93
107
  };
94
108
  };
109
+ buildChannelListOutput = () => {
110
+ const config = loadConfig();
111
+ const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
112
+ const pluginRegistry = loadPluginRegistry(config, workspaceDir);
113
+ return this.channelListView.build({
114
+ config,
115
+ workspaceDir,
116
+ pluginBindings: resolveChannelBindings(pluginRegistry)
117
+ });
118
+ };
95
119
  loginPluginChannel = async (config, channelContext, opts) => {
96
120
  const { binding, bindings } = channelContext;
97
121
  const login = binding.channel.auth?.login;
@@ -1,5 +1,4 @@
1
1
  import { Config } from "@nextclaw/core";
2
- import * as _$_nextclaw_openclaw_compat0 from "@nextclaw/openclaw-compat";
3
2
  import { PluginRegistry } from "@nextclaw/openclaw-compat";
4
3
 
5
4
  //#region src/commands/plugin/plugin-registry-loader.utils.d.ts
@@ -9,7 +8,7 @@ declare function loadPluginRegistryProgressively(config: Config, workspaceDir: s
9
8
  pluginId?: string;
10
9
  }) => void;
11
10
  }): Promise<PluginRegistry>;
12
- declare function discoverPluginRegistryStatus(config: Config, workspaceDir: string): _$_nextclaw_openclaw_compat0.PluginStatusReport;
11
+ declare function discoverPluginRegistryStatus(config: Config, workspaceDir: string): any;
13
12
  declare function createEmptyPluginRegistry(): PluginRegistry;
14
13
  //#endregion
15
14
  export { createEmptyPluginRegistry, discoverPluginRegistryStatus, loadPluginRegistryProgressively };
@@ -375,7 +375,8 @@ const defaultRunCommand = async (command, args) => {
375
375
  "ignore",
376
376
  "pipe",
377
377
  "pipe"
378
- ]
378
+ ],
379
+ windowsHide: true
379
380
  });
380
381
  let stdout = "";
381
382
  let stderr = "";
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { AccountCommandOptions, AccountSetUsernameCommandOptions, AgentCommandOptions, AgentsListCommandOptions, AgentsNewCommandOptions, AgentsRemoveCommandOptions, AgentsRuntimesCommandOptions, AgentsUpdateCommandOptions, ChannelsAddOptions, ChannelsLoginOptions, ConfigGetOptions, ConfigSetOptions, CronAddOptions, DoctorCommandOptions, GatewayCommandOptions, HealthProbe, LoginCommandOptions, LogsTailCommandOptions, MarketplaceSkillsRecommendCommandOptions, MarketplaceSkillsSearchCommandOptions, McpAddCommandOptions, McpDoctorOptions, McpListOptions, PluginsInfoOptions, PluginsInstallOptions, PluginsListOptions, PluginsUninstallOptions, RemoteConnectCommandOptions, RemoteDoctorCommandOptions, RemoteEnableCommandOptions, RemoteStatusCommandOptions, RequestRestartParams, RuntimeStatusReport, SecretsApplyOptions, SecretsAuditOptions, SecretsConfigureOptions, SecretsReloadOptions, ServiceAutostartCommandOptions, SkillsInfoCommandOptions, SkillsInstalledCommandOptions, StartCommandOptions, StatusCommandOptions, UiCommandOptions, UpdateCommandOptions, UsageCommandOptions } from "./shared/types/cli.types.js";
1
+ import { AccountCommandOptions, AccountSetUsernameCommandOptions, AgentCommandOptions, AgentsListCommandOptions, AgentsNewCommandOptions, AgentsRemoveCommandOptions, AgentsRuntimesCommandOptions, AgentsUpdateCommandOptions, ChannelsAddOptions, ChannelsListOptions, ChannelsLoginOptions, ConfigGetOptions, ConfigSetOptions, CronAddOptions, DoctorCommandOptions, GatewayCommandOptions, HealthProbe, LoginCommandOptions, LogsTailCommandOptions, MarketplaceSkillsRecommendCommandOptions, MarketplaceSkillsSearchCommandOptions, McpAddCommandOptions, McpDoctorOptions, McpListOptions, PluginsInfoOptions, PluginsInstallOptions, PluginsListOptions, PluginsUninstallOptions, RemoteConnectCommandOptions, RemoteDoctorCommandOptions, RemoteEnableCommandOptions, RemoteStatusCommandOptions, RequestRestartParams, RuntimeStatusReport, SecretsApplyOptions, SecretsAuditOptions, SecretsConfigureOptions, SecretsReloadOptions, ServiceAutostartCommandOptions, SkillsInfoCommandOptions, SkillsInstalledCommandOptions, StartCommandOptions, StatusCommandOptions, UiCommandOptions, UpdateCommandOptions, UsageCommandOptions } from "./shared/types/cli.types.js";
2
2
  import { NextclawServiceRuntime, NextclawServiceRuntimeOptions, runNextclawNpmRuntimeLauncher } from "./service-runtime.service.js";
3
3
  import { NextclawDistribution } from "./shared/types/distribution.types.js";
4
4
  import { NextclawDistributionService } from "./shared/services/runtime/nextclaw-distribution.service.js";
5
5
  import { readLearningLoopRuntimeConfig } from "@nextclaw/kernel";
6
- export { AccountCommandOptions, AccountSetUsernameCommandOptions, AgentCommandOptions, AgentsListCommandOptions, AgentsNewCommandOptions, AgentsRemoveCommandOptions, AgentsRuntimesCommandOptions, AgentsUpdateCommandOptions, ChannelsAddOptions, ChannelsLoginOptions, ConfigGetOptions, ConfigSetOptions, CronAddOptions, DoctorCommandOptions, GatewayCommandOptions, HealthProbe, LoginCommandOptions, LogsTailCommandOptions, MarketplaceSkillsRecommendCommandOptions, MarketplaceSkillsSearchCommandOptions, McpAddCommandOptions, McpDoctorOptions, McpListOptions, type NextclawDistribution, NextclawDistributionService, NextclawServiceRuntime, type NextclawServiceRuntimeOptions, PluginsInfoOptions, PluginsInstallOptions, PluginsListOptions, PluginsUninstallOptions, RemoteConnectCommandOptions, RemoteDoctorCommandOptions, RemoteEnableCommandOptions, RemoteStatusCommandOptions, RequestRestartParams, RuntimeStatusReport, SecretsApplyOptions, SecretsAuditOptions, SecretsConfigureOptions, SecretsReloadOptions, ServiceAutostartCommandOptions, SkillsInfoCommandOptions, SkillsInstalledCommandOptions, StartCommandOptions, StatusCommandOptions, UiCommandOptions, UpdateCommandOptions, UsageCommandOptions, readLearningLoopRuntimeConfig, runNextclawNpmRuntimeLauncher };
6
+ export { AccountCommandOptions, AccountSetUsernameCommandOptions, AgentCommandOptions, AgentsListCommandOptions, AgentsNewCommandOptions, AgentsRemoveCommandOptions, AgentsRuntimesCommandOptions, AgentsUpdateCommandOptions, ChannelsAddOptions, ChannelsListOptions, ChannelsLoginOptions, ConfigGetOptions, ConfigSetOptions, CronAddOptions, DoctorCommandOptions, GatewayCommandOptions, HealthProbe, LoginCommandOptions, LogsTailCommandOptions, MarketplaceSkillsRecommendCommandOptions, MarketplaceSkillsSearchCommandOptions, McpAddCommandOptions, McpDoctorOptions, McpListOptions, type NextclawDistribution, NextclawDistributionService, NextclawServiceRuntime, type NextclawServiceRuntimeOptions, PluginsInfoOptions, PluginsInstallOptions, PluginsListOptions, PluginsUninstallOptions, RemoteConnectCommandOptions, RemoteDoctorCommandOptions, RemoteEnableCommandOptions, RemoteStatusCommandOptions, RequestRestartParams, RuntimeStatusReport, SecretsApplyOptions, SecretsAuditOptions, SecretsConfigureOptions, SecretsReloadOptions, ServiceAutostartCommandOptions, SkillsInfoCommandOptions, SkillsInstalledCommandOptions, StartCommandOptions, StatusCommandOptions, UiCommandOptions, UpdateCommandOptions, UsageCommandOptions, readLearningLoopRuntimeConfig, runNextclawNpmRuntimeLauncher };
@@ -6,8 +6,8 @@ import { inferDefaultNpmRuntimeReleaseChannel } from "./npm-runtime-update-sourc
6
6
  import { NpmRuntimeUpdateStateStore } from "./npm-runtime-update-state.store.js";
7
7
  import { createExternalCommandEnv } from "@nextclaw/core";
8
8
  import { dirname, resolve } from "node:path";
9
- import { fileURLToPath } from "node:url";
10
9
  import { spawnSync } from "node:child_process";
10
+ import { fileURLToPath } from "node:url";
11
11
  //#region src/launcher/npm-runtime-launcher.service.ts
12
12
  var NpmRuntimeLauncher = class {
13
13
  env;
@@ -24,7 +24,8 @@ var NpmRuntimeLauncher = class {
24
24
  env: {
25
25
  ...createExternalCommandEnv(this.env),
26
26
  NEXTCLAW_RUNTIME_BUNDLE_CHILD: "1"
27
- }
27
+ },
28
+ windowsHide: true
28
29
  });
29
30
  process.exit(typeof result.status === "number" ? result.status : 1);
30
31
  };
@@ -2,6 +2,7 @@ import { RestartCoordinator } from "./shared/services/restart/restart-coordinato
2
2
  import { initializeConfigIfMissing } from "./shared/services/runtime/runtime-config-init.service.js";
3
3
  import { writeRestartSentinel } from "./shared/services/restart/restart-sentinel.service.js";
4
4
  import { createTopLevelNextclawCommandEnv } from "./shared/utils/top-level-nextclaw-command-env.utils.js";
5
+ import { resolveCliSubcommandLaunch } from "./shared/utils/marketplace/cli-subcommand-launch.utils.js";
5
6
  import { logStartupTrace, measureStartupSync } from "./shared/utils/startup-trace.js";
6
7
  import { isProcessRunning } from "./shared/utils/cli.utils.js";
7
8
  import { NextclawDistributionService } from "./shared/services/runtime/nextclaw-distribution.service.js";
@@ -43,10 +44,9 @@ import "./cli/commands/usage/index.js";
43
44
  import { APP_NAME, DEFAULT_WORKSPACE_DIR, DEFAULT_WORKSPACE_PATH, expandHome, getConfigPath, getDataDir, getWorkspacePath, loadConfig, resolveConfigSecrets, saveConfig } from "@nextclaw/core";
44
45
  import { NextclawKernel } from "@nextclaw/kernel";
45
46
  import { RemoteRuntimeActions } from "@nextclaw/remote";
46
- import { getPluginChannelBindings, resolvePluginChannelMessageToolHints, setPluginRuntimeBridge } from "@nextclaw/openclaw-compat";
47
+ import { getPluginChannelBindings, setPluginRuntimeBridge } from "@nextclaw/openclaw-compat";
47
48
  import { existsSync, mkdirSync } from "node:fs";
48
49
  import { join } from "node:path";
49
- import { fileURLToPath } from "node:url";
50
50
  import { spawn } from "node:child_process";
51
51
  //#region src/service-runtime.service.ts
52
52
  const FORCED_PUBLIC_UI_HOST = "0.0.0.0";
@@ -204,12 +204,16 @@ var NextclawServiceRuntime = class {
204
204
  if (!state || state.pid !== process.pid) return;
205
205
  const uiPort = typeof state.uiPort === "number" && Number.isFinite(state.uiPort) ? state.uiPort : 55667;
206
206
  const delayMs = typeof requestedDelayMs === "number" && Number.isFinite(requestedDelayMs) ? Math.max(0, Math.floor(requestedDelayMs)) : 100;
207
- const startArgs = [
208
- process.env.NEXTCLAW_SELF_RELAUNCH_CLI?.trim() || fileURLToPath(new URL("./index.js", import.meta.url)),
209
- "start",
210
- "--ui-port",
211
- String(uiPort)
212
- ];
207
+ const launch = resolveCliSubcommandLaunch({
208
+ argvEntry: process.argv[1],
209
+ importMetaUrl: import.meta.url,
210
+ cliArgs: [
211
+ "start",
212
+ "--ui-port",
213
+ String(uiPort)
214
+ ],
215
+ nodePath: process.execPath
216
+ });
213
217
  const serviceStatePath = managedServiceStateStore.path;
214
218
  const helperScript = [
215
219
  "const { spawnSync } = require(\"node:child_process\");",
@@ -218,9 +222,8 @@ var NextclawServiceRuntime = class {
218
222
  `const delayMs = ${delayMs};`,
219
223
  "const maxWaitMs = 120000;",
220
224
  "const retryIntervalMs = 1000;",
221
- "const startTimeoutMs = 60000;",
222
- `const nodePath = ${JSON.stringify(process.execPath)};`,
223
- `const startArgs = ${JSON.stringify(startArgs)};`,
225
+ `const command = ${JSON.stringify(launch.command)};`,
226
+ `const args = ${JSON.stringify(launch.args)};`,
224
227
  `const serviceStatePath = ${JSON.stringify(serviceStatePath)};`,
225
228
  "function isRunning(pid) {",
226
229
  " try {",
@@ -241,20 +244,17 @@ var NextclawServiceRuntime = class {
241
244
  " }",
242
245
  "}",
243
246
  "function tryStart() {",
244
- " spawnSync(nodePath, startArgs, {",
247
+ " spawnSync(command, args, {",
245
248
  " stdio: \"ignore\",",
246
249
  " env: process.env,",
247
- " timeout: startTimeoutMs",
250
+ " timeout: 60000,",
251
+ " windowsHide: true",
248
252
  " });",
249
253
  "}",
250
254
  "setTimeout(() => {",
251
255
  " const startedAt = Date.now();",
252
256
  " const tick = () => {",
253
- " if (hasReplacementService()) {",
254
- " process.exit(0);",
255
- " return;",
256
- " }",
257
- " if (Date.now() - startedAt >= maxWaitMs) {",
257
+ " if (hasReplacementService() || Date.now() - startedAt >= maxWaitMs) {",
258
258
  " process.exit(0);",
259
259
  " return;",
260
260
  " }",
@@ -272,7 +272,8 @@ var NextclawServiceRuntime = class {
272
272
  spawn(process.execPath, ["-e", helperScript], {
273
273
  detached: true,
274
274
  stdio: "ignore",
275
- env: createTopLevelNextclawCommandEnv(process.env)
275
+ env: createTopLevelNextclawCommandEnv(process.env),
276
+ windowsHide: true
276
277
  }).unref();
277
278
  this.selfRelaunchArmed = true;
278
279
  console.warn(`Gateway self-restart armed (${reason}).`);
@@ -361,14 +362,7 @@ var NextclawServiceRuntime = class {
361
362
  config,
362
363
  kernel,
363
364
  providerManager,
364
- extensionRegistry,
365
- loadResolvedConfig: () => resolveConfigSecrets(loadConfig(), { configPath }),
366
- resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
367
- registry: pluginRegistry,
368
- channel,
369
- cfg: resolveConfigSecrets(loadConfig(), { configPath }),
370
- accountId
371
- })
365
+ extensionRegistry
372
366
  });
373
367
  } finally {
374
368
  setPluginRuntimeBridge(null);
@@ -22,6 +22,9 @@ type ExtensionManifest = {
22
22
  configSchema?: Record<string, unknown>;
23
23
  configUiHints?: Record<string, Record<string, unknown>>;
24
24
  auth?: boolean | Record<string, unknown>;
25
+ outbound?: {
26
+ text?: boolean;
27
+ };
25
28
  }>;
26
29
  };
27
30
  };
@@ -37,8 +40,12 @@ type ExtensionLifecycleServiceOptions = {
37
40
  };
38
41
  declare class ExtensionManifestDiscoveryService {
39
42
  readonly discover: (roots: string[]) => Promise<ExtensionManifest[]>;
43
+ readonly discoverSync: (roots: string[]) => ExtensionManifest[];
40
44
  private readonly discoverRoot;
45
+ private readonly discoverRootSync;
41
46
  private readonly readManifestIfExists;
47
+ private readonly readManifestIfExistsSync;
48
+ private readonly readDirectoriesSync;
42
49
  }
43
50
  declare class ExtensionLifecycleService {
44
51
  private readonly options;
@@ -1,3 +1,4 @@
1
+ import { readFileSync, readdirSync } from "node:fs";
1
2
  import { dirname, join } from "node:path";
2
3
  import { spawn } from "node:child_process";
3
4
  import { readFile, readdir } from "node:fs/promises";
@@ -62,12 +63,22 @@ var ExtensionManifestDiscoveryService = class {
62
63
  for (const root of roots) manifests.push(...await this.discoverRoot(root));
63
64
  return manifests;
64
65
  };
66
+ discoverSync = (roots) => {
67
+ const manifests = [];
68
+ for (const root of roots) manifests.push(...this.discoverRootSync(root));
69
+ return manifests;
70
+ };
65
71
  discoverRoot = async (root) => {
66
72
  const directManifest = await this.readManifestIfExists(join(root, EXTENSION_MANIFEST_FILE));
67
73
  if (directManifest) return [directManifest];
68
74
  const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
69
75
  return (await Promise.all(entries.filter((entry) => entry.isDirectory()).map((entry) => this.readManifestIfExists(join(root, entry.name, EXTENSION_MANIFEST_FILE))))).filter((manifest) => Boolean(manifest));
70
76
  };
77
+ discoverRootSync = (root) => {
78
+ const directManifest = this.readManifestIfExistsSync(join(root, EXTENSION_MANIFEST_FILE));
79
+ if (directManifest) return [directManifest];
80
+ return this.readDirectoriesSync(root).map((entry) => this.readManifestIfExistsSync(join(root, entry, EXTENSION_MANIFEST_FILE))).filter((manifest) => Boolean(manifest));
81
+ };
71
82
  readManifestIfExists = async (path) => {
72
83
  try {
73
84
  return toManifest(JSON.parse(await readFile(path, "utf-8")), dirname(path));
@@ -76,6 +87,21 @@ var ExtensionManifestDiscoveryService = class {
76
87
  throw error;
77
88
  }
78
89
  };
90
+ readManifestIfExistsSync = (path) => {
91
+ try {
92
+ return toManifest(JSON.parse(readFileSync(path, "utf-8")), dirname(path));
93
+ } catch (error) {
94
+ if (error.code === "ENOENT") return null;
95
+ throw error;
96
+ }
97
+ };
98
+ readDirectoriesSync = (root) => {
99
+ try {
100
+ return readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
101
+ } catch {
102
+ return [];
103
+ }
104
+ };
79
105
  };
80
106
  var ExtensionLifecycleService = class {
81
107
  processes = /* @__PURE__ */ new Map();
@@ -88,13 +114,17 @@ var ExtensionLifecycleService = class {
88
114
  }
89
115
  startAll = async (manifests) => {
90
116
  const started = [];
91
- for (const manifest of manifests) started.push(this.start(manifest));
117
+ for (const manifest of manifests) try {
118
+ started.push(this.start(manifest));
119
+ } catch (error) {
120
+ this.logger.warn(`Extension ${manifest.id} failed to start: ${error instanceof Error ? error.message : String(error)}`);
121
+ }
92
122
  return started;
93
123
  };
94
124
  start = (manifest) => {
95
125
  const existing = this.processes.get(manifest.id);
96
126
  if (existing) return existing;
97
- const child = this.spawnProcess(manifest.server.command, manifest.server.args ?? [], {
127
+ const child = this.spawnProcess(manifest.server.command === "node" || manifest.server.command === "node.exe" ? process.execPath : manifest.server.command, manifest.server.args ?? [], {
98
128
  cwd: manifest.rootDir,
99
129
  env: {
100
130
  ...process.env,
@@ -108,7 +138,8 @@ var ExtensionLifecycleService = class {
108
138
  "ignore",
109
139
  "ignore",
110
140
  "inherit"
111
- ]
141
+ ],
142
+ windowsHide: true
112
143
  });
113
144
  const running = {
114
145
  manifest,
@@ -44,6 +44,7 @@ declare class ServiceExtensionRuntime {
44
44
  private readonly toContributions;
45
45
  private readonly readConfigUiHints;
46
46
  private readonly createChannelAuth;
47
+ private readonly createChannelOutbound;
47
48
  private readonly requestExtension;
48
49
  private readonly assertAuthorized;
49
50
  }
@@ -2,7 +2,7 @@ import { resolveDevFirstPartyPluginDir } from "../../../commands/plugin/developm
2
2
  import { ExtensionLifecycleService, ExtensionManifestDiscoveryService } from "./extension-lifecycle.service.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { getDataPath } from "@nextclaw/core";
5
- import { existsSync, readFileSync, readdirSync } from "node:fs";
5
+ import { existsSync, readFileSync } from "node:fs";
6
6
  import { dirname, join, resolve } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { randomUUID } from "node:crypto";
@@ -13,7 +13,7 @@ const EXTENSION_REQUEST_EVENT_TYPE = "extension.request";
13
13
  const EXTENSION_RESPONSE_INGRESS_TYPE = "extension.response";
14
14
  const serviceRequire = createRequire(import.meta.url);
15
15
  const EXTENSION_REQUEST_TIMEOUT_MS = 6e4;
16
- var ExtensionChannelAuthClient = class {
16
+ var ExtensionChannelClient = class {
17
17
  constructor(params) {
18
18
  this.params = params;
19
19
  }
@@ -45,6 +45,16 @@ var ExtensionChannelAuthClient = class {
45
45
  sessionId
46
46
  }
47
47
  });
48
+ sendText = async ({ to, text, accountId }) => await this.params.request({
49
+ extensionId: this.params.extensionId,
50
+ kind: "channel.outbound.sendText",
51
+ payload: {
52
+ channelId: this.params.channelId,
53
+ to,
54
+ text,
55
+ accountId
56
+ }
57
+ });
48
58
  };
49
59
  function uniquePaths(paths) {
50
60
  const seen = /* @__PURE__ */ new Set();
@@ -67,6 +77,7 @@ function findExtensionManifestRoot(startPath) {
67
77
  }
68
78
  }
69
79
  function readBuiltinExtensionPackages() {
80
+ if (process.env.NEXTCLAW_DISABLE_BUILTIN_EXTENSIONS === "1") return [];
70
81
  const packageJsonPath = resolve(dirname(fileURLToPath(import.meta.url)), "../../../..", "package.json");
71
82
  try {
72
83
  const packages = JSON.parse(readFileSync(packageJsonPath, "utf-8")).nextclaw?.builtinExtensions;
@@ -75,43 +86,12 @@ function readBuiltinExtensionPackages() {
75
86
  return [];
76
87
  }
77
88
  }
78
- function readPackageName(packageJsonPath) {
79
- try {
80
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
81
- return typeof packageJson.name === "string" ? packageJson.name : void 0;
82
- } catch {
83
- return;
84
- }
85
- }
86
- function readDirectoryNames(root) {
87
- try {
88
- return readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => String(entry.name));
89
- } catch {
90
- return [];
91
- }
92
- }
93
- function findWorkspacePackageRoot(packageName) {
94
- const roots = [
95
- resolve(process.cwd(), "packages", "extensions"),
96
- resolve(process.cwd(), "packages"),
97
- resolve(process.cwd(), "apps"),
98
- resolve(process.cwd(), "workers")
99
- ];
100
- for (const root of roots) for (const name of readDirectoryNames(root)) {
101
- const candidateRoot = join(root, name);
102
- if (readPackageName(join(candidateRoot, "package.json")) === packageName && existsSync(join(candidateRoot, "nextclaw.extension.json"))) return candidateRoot;
103
- }
104
- }
105
89
  function resolveBuiltinExtensionManifestRoots() {
106
90
  const roots = [];
107
- for (const packageName of readBuiltinExtensionPackages()) {
108
- try {
109
- const root = findExtensionManifestRoot(dirname(serviceRequire.resolve(packageName)));
110
- if (root) roots.push(root);
111
- } catch {}
112
- const workspaceRoot = findWorkspacePackageRoot(packageName);
113
- if (workspaceRoot) roots.push(workspaceRoot);
114
- }
91
+ for (const packageName of readBuiltinExtensionPackages()) try {
92
+ const root = findExtensionManifestRoot(dirname(serviceRequire.resolve(packageName)));
93
+ if (root) roots.push(root);
94
+ } catch {}
115
95
  return uniquePaths(roots);
116
96
  }
117
97
  function readRecord(value) {
@@ -281,7 +261,8 @@ var ServiceExtensionRuntime = class {
281
261
  schema: channel.configSchema,
282
262
  ...configUiHints ? { uiHints: configUiHints } : {}
283
263
  } } : {},
284
- ...channel.auth ? { auth: this.createChannelAuth(manifest.id, channelId) } : {}
264
+ ...channel.auth ? { auth: this.createChannelAuth(manifest.id, channelId) } : {},
265
+ ...channel.outbound?.text ? { outbound: this.createChannelOutbound(manifest.id, channelId) } : {}
285
266
  }
286
267
  });
287
268
  uiMetadata.push({
@@ -300,7 +281,12 @@ var ServiceExtensionRuntime = class {
300
281
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
301
282
  return value;
302
283
  };
303
- createChannelAuth = (extensionId, channelId) => new ExtensionChannelAuthClient({
284
+ createChannelAuth = (extensionId, channelId) => new ExtensionChannelClient({
285
+ extensionId,
286
+ channelId,
287
+ request: this.requestExtension
288
+ });
289
+ createChannelOutbound = (extensionId, channelId) => new ExtensionChannelClient({
304
290
  extensionId,
305
291
  channelId,
306
292
  request: this.requestExtension
@@ -3,9 +3,9 @@ import { openBrowser, resolveUiConfig, resolveUiStaticDir } from "../../utils/cl
3
3
  import { NextclawDistributionService } from "../runtime/nextclaw-distribution.service.js";
4
4
  import { managedServiceStateStore } from "../../stores/managed-service-state.store.js";
5
5
  import { resolveChannelConfigView } from "../../../commands/channel/channel-config-view.js";
6
+ import { ServiceExtensionRuntime } from "../extensions/service-extension-runtime.service.js";
6
7
  import { localUiRuntimeStore } from "../../stores/local-ui-runtime.store.js";
7
8
  import { GatewayControllerImpl } from "../../controllers/gateway.controller.js";
8
- import { ServiceExtensionRuntime } from "../extensions/service-extension-runtime.service.js";
9
9
  import { ServiceFileWatcherRegistry, markLocalUiRuntimeIfStarted, startGatewayRuntimeSupport, watchServiceConfigFile } from "./service-startup-support.service.js";
10
10
  import { GatewayPluginManager } from "./managers/gateway-plugin.manager.js";
11
11
  import { GatewayRemoteManager } from "./managers/gateway-remote.manager.js";
@@ -1,6 +1,6 @@
1
+ import { resolveCliSubcommandEntry } from "../../utils/marketplace/cli-subcommand-launch.utils.js";
1
2
  import { isLoopbackHost, resolvePublicIp, resolveUiStaticDir } from "../../utils/cli.utils.js";
2
3
  import { NextclawDistributionService } from "./nextclaw-distribution.service.js";
3
- import { resolveCliSubcommandEntry } from "../../utils/marketplace/cli-subcommand-launch.utils.js";
4
4
  import { describeUnmanagedHealthyTargetMessage, inspectUiTarget } from "../../utils/service-port-probe.utils.js";
5
5
  import { ManagedServiceCommandService } from "./service-managed-startup.service.js";
6
6
  import { buildMarketplaceSkillInstallArgs, pickUserFacingCommandSummary } from "../../utils/marketplace/service-marketplace-helpers.utils.js";
@@ -86,7 +86,8 @@ var RuntimeCommandService = class {
86
86
  "ignore",
87
87
  "pipe",
88
88
  "pipe"
89
- ]
89
+ ],
90
+ windowsHide: true
90
91
  });
91
92
  let stdout = "";
92
93
  let stderr = "";
@@ -1,8 +1,8 @@
1
1
  import { createTopLevelNextclawCommandEnv } from "../../utils/top-level-nextclaw-command-env.utils.js";
2
+ import { resolveCliSubcommandLaunch } from "../../utils/marketplace/cli-subcommand-launch.utils.js";
2
3
  import { isProcessRunning, openBrowser, resolveServiceLogPath, resolveUiApiBase, resolveUiConfig, waitForExit } from "../../utils/cli.utils.js";
3
4
  import { managedServiceStateStore } from "../../stores/managed-service-state.store.js";
4
5
  import { localUiRuntimeStore } from "../../stores/local-ui-runtime.store.js";
5
- import { resolveCliSubcommandLaunch } from "../../utils/marketplace/cli-subcommand-launch.utils.js";
6
6
  import { writeInitialManagedServiceState, writeReadyManagedServiceState } from "./utils/service-remote-runtime.utils.js";
7
7
  import { resolveManagedServiceReadySnapshot, resolveManagedServiceUiBinding, resolveSessionRouteCandidate } from "./utils/managed-service-routing.utils.js";
8
8
  import { probeHealthEndpoint } from "../../utils/service-port-probe.utils.js";
@@ -39,7 +39,8 @@ function spawnManagedService(params) {
39
39
  const child = spawn(cliLaunch.command, childArgs, {
40
40
  env: createTopLevelNextclawCommandEnv(process.env),
41
41
  stdio: "ignore",
42
- detached: true
42
+ detached: true,
43
+ windowsHide: true
43
44
  });
44
45
  appendStartupStage(logPath, `spawned background process pid=${child.pid ?? "unknown"}`);
45
46
  if (!child.pid) {
@@ -36,7 +36,7 @@ var DeferredUiNcpAgentControllerOwner = class {
36
36
  },
37
37
  send: async (envelope) => {
38
38
  if (!this.activeAgent) throw createUnavailableError();
39
- await this.activeAgent.agentClientEndpoint.send(envelope);
39
+ return await this.activeAgent.agentClientEndpoint.send(envelope);
40
40
  },
41
41
  stream: async (payload) => {
42
42
  if (!this.activeAgent) throw createUnavailableError();
@@ -89,6 +89,7 @@ var CompanionRuntimeService = class {
89
89
  ], {
90
90
  detached: true,
91
91
  stdio: "ignore",
92
+ windowsHide: true,
92
93
  env: {
93
94
  ...process.env,
94
95
  NEXTCLAW_COMPANION_RUNTIME_STATE_PATH: this.runtimeStore.path
@@ -2,8 +2,8 @@ import { findExecutableOnPath } from "../../utils/cli.utils.js";
2
2
  import { APP_NAME, getDataDir } from "@nextclaw/core";
3
3
  import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { dirname, join, resolve } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
5
  import { spawnSync } from "node:child_process";
6
+ import { fileURLToPath } from "node:url";
7
7
  //#region src/shared/services/workspace/workspace-manager.service.ts
8
8
  var WorkspaceManager = class {
9
9
  constructor(logo) {
@@ -126,6 +126,9 @@ type ChannelsAddOptions = {
126
126
  url?: string;
127
127
  httpUrl?: string;
128
128
  };
129
+ type ChannelsListOptions = {
130
+ json?: boolean;
131
+ };
129
132
  type ChannelsLoginOptions = {
130
133
  channel?: string;
131
134
  account?: string;
@@ -280,4 +283,4 @@ type RequestRestartParams = {
280
283
  mode?: "execute" | "notify";
281
284
  };
282
285
  //#endregion
283
- export { AccountCommandOptions, AccountSetUsernameCommandOptions, AgentCommandOptions, AgentsListCommandOptions, AgentsNewCommandOptions, AgentsRemoveCommandOptions, AgentsRuntimesCommandOptions, AgentsUpdateCommandOptions, ChannelsAddOptions, ChannelsLoginOptions, ConfigGetOptions, ConfigSetOptions, CronAddOptions, DoctorCommandOptions, GatewayCommandOptions, HealthProbe, LoginCommandOptions, LogsTailCommandOptions, MarketplaceSkillsRecommendCommandOptions, MarketplaceSkillsSearchCommandOptions, McpAddCommandOptions, McpDoctorOptions, McpListOptions, PluginsInfoOptions, PluginsInstallOptions, PluginsListOptions, PluginsUninstallOptions, type RemoteConnectCommandOptions, type RemoteDoctorCommandOptions, type RemoteEnableCommandOptions, type RemoteStatusCommandOptions, RequestRestartParams, RuntimeStatusReport, SecretsApplyOptions, SecretsAuditOptions, SecretsConfigureOptions, SecretsReloadOptions, ServiceAutostartCommandOptions, SkillsInfoCommandOptions, SkillsInstalledCommandOptions, StartCommandOptions, StatusCommandOptions, UiCommandOptions, UpdateCommandOptions, UsageCommandOptions };
286
+ export { AccountCommandOptions, AccountSetUsernameCommandOptions, AgentCommandOptions, AgentsListCommandOptions, AgentsNewCommandOptions, AgentsRemoveCommandOptions, AgentsRuntimesCommandOptions, AgentsUpdateCommandOptions, ChannelsAddOptions, ChannelsListOptions, ChannelsLoginOptions, ConfigGetOptions, ConfigSetOptions, CronAddOptions, DoctorCommandOptions, GatewayCommandOptions, HealthProbe, LoginCommandOptions, LogsTailCommandOptions, MarketplaceSkillsRecommendCommandOptions, MarketplaceSkillsSearchCommandOptions, McpAddCommandOptions, McpDoctorOptions, McpListOptions, PluginsInfoOptions, PluginsInstallOptions, PluginsListOptions, PluginsUninstallOptions, type RemoteConnectCommandOptions, type RemoteDoctorCommandOptions, type RemoteEnableCommandOptions, type RemoteStatusCommandOptions, RequestRestartParams, RuntimeStatusReport, SecretsApplyOptions, SecretsAuditOptions, SecretsConfigureOptions, SecretsReloadOptions, ServiceAutostartCommandOptions, SkillsInfoCommandOptions, SkillsInstalledCommandOptions, StartCommandOptions, StatusCommandOptions, UiCommandOptions, UpdateCommandOptions, UsageCommandOptions };
@@ -193,7 +193,8 @@ function openBrowser(url) {
193
193
  spawn(command, args, {
194
194
  stdio: "ignore",
195
195
  detached: true,
196
- env: createExternalCommandEnv(process.env)
196
+ env: createExternalCommandEnv(process.env),
197
+ windowsHide: true
197
198
  }).unref();
198
199
  return true;
199
200
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/service",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "private": false,
5
5
  "description": "NextClaw long-running service host and runtime lifecycle.",
6
6
  "type": "module",
@@ -35,24 +35,24 @@
35
35
  "commander": "^12.1.0",
36
36
  "jszip": "^3.10.1",
37
37
  "yaml": "^2.8.1",
38
- "@nextclaw/channel-extension-feishu": "0.1.1",
39
- "@nextclaw/core": "0.12.17",
40
- "@nextclaw/channel-extension-weixin": "0.1.4",
41
- "@nextclaw/ncp-agent-runtime": "0.3.20",
42
- "@nextclaw/ncp": "0.5.10",
43
- "@nextclaw/ncp-mcp": "0.1.84",
44
- "@nextclaw/ncp-http-agent-server": "0.3.22",
45
- "@nextclaw/nextclaw-hermes-acp-bridge": "0.1.9",
46
- "@nextclaw/ncp-toolkit": "0.5.15",
47
- "@nextclaw/mcp": "0.1.82",
48
- "@nextclaw/kernel": "0.1.6",
49
- "@nextclaw/nextclaw-ncp-runtime-stdio-client": "0.1.10",
50
- "@nextclaw/nextclaw-ncp-runtime-http-client": "0.1.9",
51
- "@nextclaw/openclaw-compat": "1.0.17",
52
- "@nextclaw/runtime": "0.2.49",
53
- "@nextclaw/shared": "0.1.4",
54
- "@nextclaw/remote": "0.1.94",
55
- "@nextclaw/server": "0.12.17"
38
+ "@nextclaw/channel-extension-feishu": "0.1.3",
39
+ "@nextclaw/kernel": "0.1.8",
40
+ "@nextclaw/mcp": "0.1.84",
41
+ "@nextclaw/core": "0.12.19",
42
+ "@nextclaw/channel-extension-weixin": "0.1.6",
43
+ "@nextclaw/ncp": "0.5.12",
44
+ "@nextclaw/ncp-agent-runtime": "0.3.22",
45
+ "@nextclaw/nextclaw-hermes-acp-bridge": "0.1.11",
46
+ "@nextclaw/ncp-http-agent-server": "0.3.24",
47
+ "@nextclaw/ncp-toolkit": "0.5.17",
48
+ "@nextclaw/nextclaw-ncp-runtime-http-client": "0.1.11",
49
+ "@nextclaw/ncp-mcp": "0.1.86",
50
+ "@nextclaw/nextclaw-ncp-runtime-stdio-client": "0.1.12",
51
+ "@nextclaw/remote": "0.1.96",
52
+ "@nextclaw/shared": "0.1.6",
53
+ "@nextclaw/runtime": "0.2.51",
54
+ "@nextclaw/openclaw-compat": "1.0.19",
55
+ "@nextclaw/server": "0.12.19"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/node": "^20.17.6",