@nextclaw/service 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/cli/commands/agent/agent-runtime.utils.d.ts +15 -0
- package/dist/cli/commands/agent/agent-runtime.utils.js +85 -0
- package/dist/cli/commands/agent/cli-agent-runner.utils.d.ts +21 -0
- package/dist/cli/commands/agent/cli-agent-runner.utils.js +89 -0
- package/dist/cli/commands/agent/index.d.ts +3 -0
- package/dist/cli/commands/agent/index.js +3 -0
- package/dist/cli/commands/agent/services/agent-commands.service.d.ts +17 -0
- package/dist/cli/commands/agent/services/agent-commands.service.js +112 -0
- package/dist/cli/commands/companion/index.d.ts +15 -0
- package/dist/cli/commands/companion/index.js +24 -0
- package/dist/cli/commands/companion/services/companion-process.service.d.ts +17 -0
- package/dist/cli/commands/companion/services/companion-process.service.js +49 -0
- package/dist/cli/commands/config/index.d.ts +2 -0
- package/dist/cli/commands/config/index.js +2 -0
- package/dist/cli/commands/config/services/config-commands.service.d.ts +18 -0
- package/dist/cli/commands/config/services/config-commands.service.js +133 -0
- package/dist/cli/commands/cron/index.d.ts +2 -0
- package/dist/cli/commands/cron/index.js +2 -0
- package/dist/cli/commands/cron/services/cron-commands.service.d.ts +22 -0
- package/dist/cli/commands/cron/services/cron-commands.service.js +107 -0
- package/dist/cli/commands/cron/services/cron-local.service.d.ts +25 -0
- package/dist/cli/commands/cron/services/cron-local.service.js +95 -0
- package/dist/cli/commands/cron/utils/cron-job.utils.d.ts +31 -0
- package/dist/cli/commands/cron/utils/cron-job.utils.js +15 -0
- package/dist/cli/commands/diagnostics/index.d.ts +2 -0
- package/dist/cli/commands/diagnostics/index.js +2 -0
- package/dist/cli/commands/diagnostics/services/diagnostics-commands.service.d.ts +22 -0
- package/dist/cli/commands/diagnostics/services/diagnostics-commands.service.js +319 -0
- package/dist/cli/commands/diagnostics/utils/diagnostics-render.utils.d.ts +23 -0
- package/dist/cli/commands/diagnostics/utils/diagnostics-render.utils.js +66 -0
- package/dist/cli/commands/gateway/index.d.ts +14 -0
- package/dist/cli/commands/gateway/index.js +15 -0
- package/dist/cli/commands/logs/index.d.ts +12 -0
- package/dist/cli/commands/logs/index.js +29 -0
- package/dist/cli/commands/mcp/index.d.ts +14 -0
- package/dist/cli/commands/mcp/index.js +193 -0
- package/dist/cli/commands/restart/index.d.ts +20 -0
- package/dist/cli/commands/restart/index.js +88 -0
- package/dist/cli/commands/secrets/index.d.ts +22 -0
- package/dist/cli/commands/secrets/index.js +280 -0
- package/dist/cli/commands/serve/index.d.ts +14 -0
- package/dist/cli/commands/serve/index.js +19 -0
- package/dist/cli/commands/skills/index.d.ts +26 -0
- package/dist/cli/commands/skills/index.js +147 -0
- package/dist/cli/commands/skills/marketplace-client.d.ts +31 -0
- package/dist/cli/commands/skills/marketplace-client.js +84 -0
- package/dist/cli/commands/skills/marketplace-command-options.utils.d.ts +25 -0
- package/dist/cli/commands/skills/marketplace-command-options.utils.js +31 -0
- package/dist/cli/commands/skills/marketplace-identity.utils.d.ts +14 -0
- package/dist/cli/commands/skills/marketplace-identity.utils.js +77 -0
- package/dist/cli/commands/skills/marketplace-network-retry.d.ts +4 -0
- package/dist/cli/commands/skills/marketplace-network-retry.js +32 -0
- package/dist/cli/commands/skills/marketplace.metadata.d.ts +29 -0
- package/dist/cli/commands/skills/marketplace.metadata.js +158 -0
- package/dist/cli/commands/skills/marketplace.service.d.ts +46 -0
- package/dist/cli/commands/skills/marketplace.service.js +238 -0
- package/dist/cli/commands/skills/skills-query.service.d.ts +141 -0
- package/dist/cli/commands/skills/skills-query.service.js +212 -0
- package/dist/cli/commands/start/index.d.ts +18 -0
- package/dist/cli/commands/start/index.js +25 -0
- package/dist/cli/commands/stop/index.d.ts +12 -0
- package/dist/cli/commands/stop/index.js +11 -0
- package/dist/cli/commands/ui/index.d.ts +14 -0
- package/dist/cli/commands/ui/index.js +17 -0
- package/dist/cli/commands/usage/index.d.ts +2 -0
- package/dist/cli/commands/usage/index.js +2 -0
- package/dist/cli/commands/usage/services/llm-usage-command.service.d.ts +22 -0
- package/dist/cli/commands/usage/services/llm-usage-command.service.js +160 -0
- package/dist/cli/commands/usage/services/llm-usage-query.service.d.ts +43 -0
- package/dist/cli/commands/usage/services/llm-usage-query.service.js +85 -0
- package/dist/commands/channel/channel-config-view.d.ts +7 -0
- package/dist/commands/channel/channel-config-view.js +7 -0
- package/dist/commands/channel/index.d.ts +28 -0
- package/dist/commands/channel/index.js +224 -0
- package/dist/commands/platform-auth/index.d.ts +2 -0
- package/dist/commands/platform-auth/index.js +2 -0
- package/dist/commands/platform-auth/services/account-status.service.d.ts +18 -0
- package/dist/commands/platform-auth/services/account-status.service.js +34 -0
- package/dist/commands/platform-auth/services/platform-auth-commands.service.d.ts +77 -0
- package/dist/commands/platform-auth/services/platform-auth-commands.service.js +295 -0
- package/dist/commands/platform-auth/utils/payload.utils.d.ts +28 -0
- package/dist/commands/platform-auth/utils/payload.utils.js +87 -0
- package/dist/commands/plugin/development-source/dev-plugin-overrides.utils.d.ts +18 -0
- package/dist/commands/plugin/development-source/dev-plugin-overrides.utils.js +111 -0
- package/dist/commands/plugin/development-source/first-party-plugin-load-paths.d.ts +9 -0
- package/dist/commands/plugin/development-source/first-party-plugin-load-paths.js +183 -0
- package/dist/commands/plugin/index.d.ts +30 -0
- package/dist/commands/plugin/index.js +266 -0
- package/dist/commands/plugin/plugin-command-utils.d.ts +13 -0
- package/dist/commands/plugin/plugin-command-utils.js +37 -0
- package/dist/commands/plugin/plugin-extension-registry.d.ts +10 -0
- package/dist/commands/plugin/plugin-extension-registry.js +35 -0
- package/dist/commands/plugin/plugin-mutation-actions.d.ts +15 -0
- package/dist/commands/plugin/plugin-mutation-actions.js +162 -0
- package/dist/commands/plugin/plugin-registry-loader.d.ts +15 -0
- package/dist/commands/plugin/plugin-registry-loader.js +43 -0
- package/dist/commands/plugin/plugin-reload.d.ts +13 -0
- package/dist/commands/plugin/plugin-reload.js +42 -0
- package/dist/commands/remote/index.d.ts +47 -0
- package/dist/commands/remote/index.js +174 -0
- package/dist/commands/remote/services/remote-access-host.service.d.ts +41 -0
- package/dist/commands/remote/services/remote-access-host.service.js +126 -0
- package/dist/commands/remote/services/remote-runtime-support.service.d.ts +15 -0
- package/dist/commands/remote/services/remote-runtime-support.service.js +79 -0
- package/dist/commands/remote/services/remote-service-control.service.d.ts +33 -0
- package/dist/commands/remote/services/remote-service-control.service.js +188 -0
- package/dist/commands/remote/utils/platform-api-base.utils.d.ts +14 -0
- package/dist/commands/remote/utils/platform-api-base.utils.js +39 -0
- package/dist/commands/service/index.d.ts +16 -0
- package/dist/commands/service/index.js +31 -0
- package/dist/commands/service/services/autostart/host-autostart-command.service.d.ts +29 -0
- package/dist/commands/service/services/autostart/host-autostart-command.service.js +158 -0
- package/dist/commands/service/services/autostart/host-autostart-runtime.service.d.ts +23 -0
- package/dist/commands/service/services/autostart/host-autostart-runtime.service.js +53 -0
- package/dist/commands/service/services/autostart/host-autostart.service.d.ts +41 -0
- package/dist/commands/service/services/autostart/host-autostart.service.js +48 -0
- package/dist/commands/service/services/autostart/linux-systemd-autostart.service.d.ts +48 -0
- package/dist/commands/service/services/autostart/linux-systemd-autostart.service.js +433 -0
- package/dist/commands/service/services/autostart/macos-launch-agent-autostart.service.d.ts +54 -0
- package/dist/commands/service/services/autostart/macos-launch-agent-autostart.service.js +405 -0
- package/dist/commands/service/services/autostart/windows-task-autostart.service.d.ts +54 -0
- package/dist/commands/service/services/autostart/windows-task-autostart.service.js +403 -0
- package/dist/commands/service/types/autostart/host-autostart.types.d.ts +64 -0
- package/dist/commands/service/types/autostart/host-autostart.types.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/launcher/npm-runtime-bundle-layout.store.d.ts +23 -0
- package/dist/launcher/npm-runtime-bundle-layout.store.js +37 -0
- package/dist/launcher/npm-runtime-bundle-manifest.service.d.ts +9 -0
- package/dist/launcher/npm-runtime-bundle-manifest.service.js +39 -0
- package/dist/launcher/npm-runtime-bundle.service.d.ts +47 -0
- package/dist/launcher/npm-runtime-bundle.service.js +150 -0
- package/dist/launcher/npm-runtime-bundle.types.d.ts +49 -0
- package/dist/launcher/npm-runtime-bundle.types.js +1 -0
- package/dist/launcher/npm-runtime-launcher.service.d.ts +19 -0
- package/dist/launcher/npm-runtime-launcher.service.js +57 -0
- package/dist/launcher/npm-runtime-update-command.service.d.ts +12 -0
- package/dist/launcher/npm-runtime-update-command.service.js +87 -0
- package/dist/launcher/npm-runtime-update-source.service.d.ts +19 -0
- package/dist/launcher/npm-runtime-update-source.service.js +57 -0
- package/dist/launcher/npm-runtime-update-state.store.d.ts +17 -0
- package/dist/launcher/npm-runtime-update-state.store.js +92 -0
- package/dist/launcher/npm-runtime-update.manager.d.ts +42 -0
- package/dist/launcher/npm-runtime-update.manager.js +179 -0
- package/dist/launcher/npm-runtime-update.service.d.ts +54 -0
- package/dist/launcher/npm-runtime-update.service.js +183 -0
- package/dist/service-runtime.service.d.ts +91 -0
- package/dist/service-runtime.service.js +392 -0
- package/dist/shared/controllers/gateway.controller.d.ts +61 -0
- package/dist/shared/controllers/gateway.controller.js +318 -0
- package/dist/shared/services/extensions/extension-lifecycle.service.d.ts +56 -0
- package/dist/shared/services/extensions/extension-lifecycle.service.js +143 -0
- package/dist/shared/services/extensions/service-extension-runtime.service.d.ts +51 -0
- package/dist/shared/services/extensions/service-extension-runtime.service.js +338 -0
- package/dist/shared/services/gateway/cron-job-handler.service.d.ts +26 -0
- package/dist/shared/services/gateway/cron-job-handler.service.js +100 -0
- package/dist/shared/services/gateway/gateway-restart-wake.service.d.ts +12 -0
- package/dist/shared/services/gateway/gateway-restart-wake.service.js +91 -0
- package/dist/shared/services/gateway/managers/gateway-plugin.manager.d.ts +37 -0
- package/dist/shared/services/gateway/managers/gateway-plugin.manager.js +218 -0
- package/dist/shared/services/gateway/managers/gateway-remote.manager.d.ts +20 -0
- package/dist/shared/services/gateway/managers/gateway-remote.manager.js +25 -0
- package/dist/shared/services/gateway/nextclaw-app.service.d.ts +22 -0
- package/dist/shared/services/gateway/nextclaw-app.service.js +53 -0
- package/dist/shared/services/gateway/nextclaw-gateway-runtime.service.d.ts +89 -0
- package/dist/shared/services/gateway/nextclaw-gateway-runtime.service.js +337 -0
- package/dist/shared/services/gateway/service-bootstrap-status.d.ts +33 -0
- package/dist/shared/services/gateway/service-bootstrap-status.js +152 -0
- package/dist/shared/services/gateway/service-startup-support.service.d.ts +42 -0
- package/dist/shared/services/gateway/service-startup-support.service.js +96 -0
- package/dist/shared/services/gateway/utils/gateway-runtime-lifecycle.utils.d.ts +9 -0
- package/dist/shared/services/gateway/utils/gateway-runtime-lifecycle.utils.js +10 -0
- package/dist/shared/services/marketplace/service-marketplace-installer.service.d.ts +31 -0
- package/dist/shared/services/marketplace/service-marketplace-installer.service.js +99 -0
- package/dist/shared/services/marketplace/service-mcp-marketplace-ops.d.ts +39 -0
- package/dist/shared/services/marketplace/service-mcp-marketplace-ops.js +67 -0
- package/dist/shared/services/plugin/utils/plugin-dev-hot-reload.utils.d.ts +24 -0
- package/dist/shared/services/plugin/utils/plugin-dev-hot-reload.utils.js +117 -0
- package/dist/shared/services/plugin/utils/plugin-runtime-bridge.utils.d.ts +6 -0
- package/dist/shared/services/plugin/utils/plugin-runtime-bridge.utils.js +96 -0
- package/dist/shared/services/restart/restart-coordinator.service.d.ts +30 -0
- package/dist/shared/services/restart/restart-coordinator.service.js +51 -0
- package/dist/shared/services/restart/restart-sentinel.service.d.ts +39 -0
- package/dist/shared/services/restart/restart-sentinel.service.js +88 -0
- package/dist/shared/services/restart/runtime-restart-request.service.d.ts +24 -0
- package/dist/shared/services/restart/runtime-restart-request.service.js +42 -0
- package/dist/shared/services/runtime/runtime-command.service.d.ts +37 -0
- package/dist/shared/services/runtime/runtime-command.service.js +163 -0
- package/dist/shared/services/runtime/runtime-config-init.service.d.ts +4 -0
- package/dist/shared/services/runtime/runtime-config-init.service.js +10 -0
- package/dist/shared/services/runtime/service-managed-startup.service.d.ts +146 -0
- package/dist/shared/services/runtime/service-managed-startup.service.js +426 -0
- package/dist/shared/services/runtime/service-remote-runtime.service.d.ts +53 -0
- package/dist/shared/services/runtime/service-remote-runtime.service.js +173 -0
- package/dist/shared/services/runtime/utils/skills-loader.utils.d.ts +12 -0
- package/dist/shared/services/runtime/utils/skills-loader.utils.js +9 -0
- package/dist/shared/services/session/service-deferred-ncp-agent.service.d.ts +14 -0
- package/dist/shared/services/session/service-deferred-ncp-agent.service.js +85 -0
- package/dist/shared/services/ui/companion-runtime.service.d.ts +33 -0
- package/dist/shared/services/ui/companion-runtime.service.js +145 -0
- package/dist/shared/services/ui/local-ui-discovery.service.d.ts +19 -0
- package/dist/shared/services/ui/local-ui-discovery.service.js +41 -0
- package/dist/shared/services/ui/npm-runtime-update-host.service.d.ts +40 -0
- package/dist/shared/services/ui/npm-runtime-update-host.service.js +181 -0
- package/dist/shared/services/ui/runtime-control-host.service.d.ts +28 -0
- package/dist/shared/services/ui/runtime-control-host.service.js +89 -0
- package/dist/shared/services/ui/service-remote-access.service.d.ts +25 -0
- package/dist/shared/services/ui/service-remote-access.service.js +38 -0
- package/dist/shared/services/ui/ui-bridge-api.service.d.ts +16 -0
- package/dist/shared/services/ui/ui-bridge-api.service.js +43 -0
- package/dist/shared/services/workspace/workspace-manager.service.d.ts +19 -0
- package/dist/shared/services/workspace/workspace-manager.service.js +135 -0
- package/dist/shared/stores/companion-runtime.store.d.ts +15 -0
- package/dist/shared/stores/companion-runtime.store.js +27 -0
- package/dist/shared/stores/local-ui-runtime.store.d.ts +25 -0
- package/dist/shared/stores/local-ui-runtime.store.js +54 -0
- package/dist/shared/stores/managed-service-state.store.d.ts +28 -0
- package/dist/shared/stores/managed-service-state.store.js +38 -0
- package/dist/shared/stores/pending-restart.store.d.ts +21 -0
- package/dist/shared/stores/pending-restart.store.js +35 -0
- package/dist/shared/types/cli.types.d.ts +295 -0
- package/dist/shared/types/cli.types.js +1 -0
- package/dist/shared/utils/cli.utils.d.ts +34 -0
- package/dist/shared/utils/cli.utils.js +262 -0
- package/dist/shared/utils/config-path.d.ts +15 -0
- package/dist/shared/utils/config-path.js +167 -0
- package/dist/shared/utils/marketplace/cli-subcommand-launch.utils.d.ts +16 -0
- package/dist/shared/utils/marketplace/cli-subcommand-launch.utils.js +46 -0
- package/dist/shared/utils/marketplace/service-marketplace-helpers.utils.d.ts +9 -0
- package/dist/shared/utils/marketplace/service-marketplace-helpers.utils.js +33 -0
- package/dist/shared/utils/package/package-manifest.utils.d.ts +8 -0
- package/dist/shared/utils/package/package-manifest.utils.js +48 -0
- package/dist/shared/utils/runtime-helpers.d.ts +14 -0
- package/dist/shared/utils/runtime-helpers.js +26 -0
- package/dist/shared/utils/service-port-probe.utils.d.ts +41 -0
- package/dist/shared/utils/service-port-probe.utils.js +164 -0
- package/dist/shared/utils/startup-trace.d.ts +7 -0
- package/dist/shared/utils/startup-trace.js +37 -0
- package/dist/shared/utils/top-level-nextclaw-command-env.utils.d.ts +4 -0
- package/dist/shared/utils/top-level-nextclaw-command-env.utils.js +10 -0
- package/package.json +68 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import { isProcessRunning, openBrowser, resolveServiceLogPath, resolveUiApiBase, resolveUiConfig, resolveUiStaticDir, waitForExit } from "../../utils/cli.utils.js";
|
|
2
|
+
import { localUiRuntimeStore } from "../../stores/local-ui-runtime.store.js";
|
|
3
|
+
import { managedServiceStateStore } from "../../stores/managed-service-state.store.js";
|
|
4
|
+
import { probeHealthEndpoint } from "../../utils/service-port-probe.utils.js";
|
|
5
|
+
import { resolveCliSubcommandLaunch } from "../../utils/marketplace/cli-subcommand-launch.utils.js";
|
|
6
|
+
import { createTopLevelNextclawCommandEnv } from "../../utils/top-level-nextclaw-command-env.utils.js";
|
|
7
|
+
import { writeInitialManagedServiceState, writeReadyManagedServiceState } from "./service-remote-runtime.service.js";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import { dirname } from "node:path";
|
|
10
|
+
import * as NextclawCore from "@nextclaw/core";
|
|
11
|
+
import { FileLogSink } from "@nextclaw/core";
|
|
12
|
+
import { mkdirSync } from "node:fs";
|
|
13
|
+
//#region src/shared/services/runtime/service-managed-startup.service.ts
|
|
14
|
+
const { APP_NAME: APP_NAME$1, loadConfig: loadConfig$1 } = NextclawCore;
|
|
15
|
+
function resolveManagedServiceReadySnapshot(params) {
|
|
16
|
+
const { snapshot, readLocalUiRuntimeState, isProcessRunningFn: providedIsProcessRunningFn } = params;
|
|
17
|
+
const localUiRuntimeState = (readLocalUiRuntimeState ?? localUiRuntimeStore.read)();
|
|
18
|
+
const isProcessRunningFn = providedIsProcessRunningFn ?? isProcessRunning;
|
|
19
|
+
if (!localUiRuntimeState || typeof localUiRuntimeState.pid !== "number" || !Number.isFinite(localUiRuntimeState.pid) || localUiRuntimeState.uiPort !== snapshot.uiPort || !isProcessRunningFn(localUiRuntimeState.pid)) return snapshot;
|
|
20
|
+
return {
|
|
21
|
+
...snapshot,
|
|
22
|
+
pid: localUiRuntimeState.pid,
|
|
23
|
+
uiUrl: localUiRuntimeState.uiUrl,
|
|
24
|
+
apiUrl: localUiRuntimeState.apiUrl,
|
|
25
|
+
uiHost: localUiRuntimeState.uiHost ?? snapshot.uiHost,
|
|
26
|
+
uiPort: localUiRuntimeState.uiPort ?? snapshot.uiPort
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function toObjectRecord(value) {
|
|
30
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
function hasSessionRoutingMetadata(params) {
|
|
34
|
+
const context = toObjectRecord(params.metadata.last_delivery_context) ?? {};
|
|
35
|
+
const hasPrimaryRoute = Boolean(params.normalizeOptionalString(context.channel)) && Boolean(params.normalizeOptionalString(context.chatId));
|
|
36
|
+
const hasFallbackRoute = Boolean(params.normalizeOptionalString(params.metadata.last_channel)) && Boolean(params.normalizeOptionalString(params.metadata.last_to));
|
|
37
|
+
return hasPrimaryRoute || hasFallbackRoute;
|
|
38
|
+
}
|
|
39
|
+
function resolveManagedServiceUiBinding(state) {
|
|
40
|
+
try {
|
|
41
|
+
const parsed = new URL(state.uiUrl);
|
|
42
|
+
const parsedPort = Number(parsed.port || 80);
|
|
43
|
+
return {
|
|
44
|
+
host: state.uiHost ?? parsed.hostname,
|
|
45
|
+
port: Number.isFinite(parsedPort) ? parsedPort : state.uiPort ?? 55667
|
|
46
|
+
};
|
|
47
|
+
} catch {
|
|
48
|
+
return {
|
|
49
|
+
host: state.uiHost ?? "127.0.0.1",
|
|
50
|
+
port: state.uiPort ?? 55667
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function resolveSessionRouteCandidate(params) {
|
|
55
|
+
const sessionRecord = toObjectRecord(params.session);
|
|
56
|
+
const key = params.normalizeOptionalString(sessionRecord?.key);
|
|
57
|
+
if (!key || key.startsWith("cli:")) return null;
|
|
58
|
+
if (!hasSessionRoutingMetadata({
|
|
59
|
+
metadata: toObjectRecord(sessionRecord?.metadata) ?? {},
|
|
60
|
+
normalizeOptionalString: params.normalizeOptionalString
|
|
61
|
+
})) return null;
|
|
62
|
+
const updatedAtRaw = params.normalizeOptionalString(sessionRecord?.updated_at);
|
|
63
|
+
const updatedAt = updatedAtRaw ? Date.parse(updatedAtRaw) : NaN;
|
|
64
|
+
return {
|
|
65
|
+
key,
|
|
66
|
+
updatedAt: Number.isFinite(updatedAt) ? updatedAt : 0
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function spawnManagedService(params) {
|
|
70
|
+
const { appName, config, uiConfig, uiUrl, apiUrl, healthUrl, startupTimeoutMs, resolveStartupTimeoutMs, appendStartupStage, printStartupFailureDiagnostics, resolveServiceLogPath } = params;
|
|
71
|
+
const logPath = resolveServiceLogPath();
|
|
72
|
+
new FileLogSink({ serviceLogPath: logPath }).ensureReady();
|
|
73
|
+
mkdirSync(dirname(logPath), { recursive: true });
|
|
74
|
+
const readinessTimeoutMs = resolveStartupTimeoutMs(startupTimeoutMs);
|
|
75
|
+
const quickPhaseTimeoutMs = Math.min(8e3, readinessTimeoutMs);
|
|
76
|
+
const extendedPhaseTimeoutMs = Math.max(0, readinessTimeoutMs - quickPhaseTimeoutMs);
|
|
77
|
+
appendStartupStage(logPath, `start requested: ui=${uiConfig.host}:${uiConfig.port}, readinessTimeoutMs=${readinessTimeoutMs}`);
|
|
78
|
+
console.log(`Starting ${appName} background service (readiness timeout ${Math.ceil(readinessTimeoutMs / 1e3)}s)...`);
|
|
79
|
+
const cliLaunch = resolveCliSubcommandLaunch({
|
|
80
|
+
argvEntry: process.argv[1],
|
|
81
|
+
importMetaUrl: import.meta.url,
|
|
82
|
+
cliArgs: [
|
|
83
|
+
"serve",
|
|
84
|
+
"--ui-port",
|
|
85
|
+
String(uiConfig.port)
|
|
86
|
+
],
|
|
87
|
+
nodePath: process.execPath
|
|
88
|
+
});
|
|
89
|
+
const childArgs = [...process.execArgv, ...cliLaunch.args];
|
|
90
|
+
appendStartupStage(logPath, `spawning background process: ${cliLaunch.command} ${childArgs.join(" ")}`);
|
|
91
|
+
const child = spawn(cliLaunch.command, childArgs, {
|
|
92
|
+
env: createTopLevelNextclawCommandEnv(process.env),
|
|
93
|
+
stdio: "ignore",
|
|
94
|
+
detached: true
|
|
95
|
+
});
|
|
96
|
+
appendStartupStage(logPath, `spawned background process pid=${child.pid ?? "unknown"}`);
|
|
97
|
+
if (!child.pid) {
|
|
98
|
+
appendStartupStage(logPath, "spawn failed: child pid missing");
|
|
99
|
+
console.error("Error: Failed to start background service.");
|
|
100
|
+
printStartupFailureDiagnostics({
|
|
101
|
+
uiUrl,
|
|
102
|
+
apiUrl,
|
|
103
|
+
healthUrl,
|
|
104
|
+
logPath,
|
|
105
|
+
lastProbeError: null
|
|
106
|
+
});
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const snapshot = {
|
|
110
|
+
pid: child.pid,
|
|
111
|
+
uiUrl,
|
|
112
|
+
apiUrl,
|
|
113
|
+
uiHost: uiConfig.host,
|
|
114
|
+
uiPort: uiConfig.port,
|
|
115
|
+
logPath
|
|
116
|
+
};
|
|
117
|
+
writeInitialManagedServiceState({
|
|
118
|
+
config,
|
|
119
|
+
readinessTimeoutMs,
|
|
120
|
+
snapshot
|
|
121
|
+
});
|
|
122
|
+
return {
|
|
123
|
+
child,
|
|
124
|
+
logPath,
|
|
125
|
+
readinessTimeoutMs,
|
|
126
|
+
quickPhaseTimeoutMs,
|
|
127
|
+
extendedPhaseTimeoutMs,
|
|
128
|
+
snapshot
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async function waitForManagedServiceReadiness(params) {
|
|
132
|
+
params.appendStartupStage(params.logPath, `health probe started: ${params.healthUrl} (phase=quick, timeoutMs=${params.quickPhaseTimeoutMs})`);
|
|
133
|
+
let readiness = await params.waitForBackgroundServiceReady({
|
|
134
|
+
pid: params.childPid,
|
|
135
|
+
healthUrl: params.healthUrl,
|
|
136
|
+
timeoutMs: params.quickPhaseTimeoutMs
|
|
137
|
+
});
|
|
138
|
+
if (!readiness.ready && params.isProcessRunning(params.childPid) && params.extendedPhaseTimeoutMs > 0) {
|
|
139
|
+
console.warn(`Warning: Background service is still running but not ready after ${Math.ceil(params.quickPhaseTimeoutMs / 1e3)}s; waiting up to ${Math.ceil(params.extendedPhaseTimeoutMs / 1e3)}s more.`);
|
|
140
|
+
params.appendStartupStage(params.logPath, `health probe entering extended phase (timeoutMs=${params.extendedPhaseTimeoutMs}, lastError=${readiness.lastProbeError ?? "none"})`);
|
|
141
|
+
readiness = await params.waitForBackgroundServiceReady({
|
|
142
|
+
pid: params.childPid,
|
|
143
|
+
healthUrl: params.healthUrl,
|
|
144
|
+
timeoutMs: params.extendedPhaseTimeoutMs
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (!readiness.ready && params.isProcessRunning(params.childPid)) params.appendStartupStage(params.logPath, `startup degraded: process alive but health probe timed out after ${params.readinessTimeoutMs}ms (lastError=${readiness.lastProbeError ?? "none"})`);
|
|
148
|
+
return readiness;
|
|
149
|
+
}
|
|
150
|
+
async function reportManagedServiceStart(params) {
|
|
151
|
+
if (!params.readiness.ready) {
|
|
152
|
+
const hint = params.readiness.lastProbeError ? ` Last probe error: ${params.readiness.lastProbeError}` : "";
|
|
153
|
+
console.warn(`Warning: ${params.appName} is running (PID ${params.state.pid}) but not healthy yet after ${Math.ceil(params.readinessTimeoutMs / 1e3)}s. Marked as degraded.${hint}`);
|
|
154
|
+
console.warn(`Tip: Run "${params.appName} status --json" and check logs: ${params.state.logPath}`);
|
|
155
|
+
} else console.log(`✓ ${params.appName} started in background (PID ${params.state.pid})`);
|
|
156
|
+
console.log(`UI: ${params.uiUrl}`);
|
|
157
|
+
console.log(`API: ${params.apiUrl}`);
|
|
158
|
+
await params.printPublicUiUrls(params.uiConfig.host, params.uiConfig.port);
|
|
159
|
+
console.log(`Logs: ${params.state.logPath}`);
|
|
160
|
+
params.printServiceControlHints();
|
|
161
|
+
}
|
|
162
|
+
var ManagedServiceCommandService = class {
|
|
163
|
+
loggingRuntime = NextclawCore.getLoggingRuntime();
|
|
164
|
+
serviceLogger = this.loggingRuntime.getLogger("service");
|
|
165
|
+
constructor(deps) {
|
|
166
|
+
this.deps = deps;
|
|
167
|
+
}
|
|
168
|
+
runForeground = async (options) => {
|
|
169
|
+
const uiConfig = resolveUiConfig(loadConfig$1(), options.uiOverrides);
|
|
170
|
+
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
171
|
+
if (options.open) openBrowser(uiUrl);
|
|
172
|
+
await this.deps.startGateway({
|
|
173
|
+
uiOverrides: options.uiOverrides,
|
|
174
|
+
uiStaticDir: resolveUiStaticDir()
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
startService = async (options) => {
|
|
178
|
+
this.loggingRuntime.ensureReady();
|
|
179
|
+
const { open, startupTimeoutMs, uiOverrides } = options;
|
|
180
|
+
const config = loadConfig$1();
|
|
181
|
+
const uiConfig = resolveUiConfig(config, uiOverrides);
|
|
182
|
+
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
183
|
+
const apiUrl = `${uiUrl}/api`;
|
|
184
|
+
const staticDir = resolveUiStaticDir();
|
|
185
|
+
const existing = managedServiceStateStore.read();
|
|
186
|
+
if (existing && isProcessRunning(existing.pid)) {
|
|
187
|
+
await this.handleExistingManagedService({
|
|
188
|
+
existing,
|
|
189
|
+
uiConfig,
|
|
190
|
+
options
|
|
191
|
+
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (existing) managedServiceStateStore.clear();
|
|
195
|
+
if (!staticDir) {
|
|
196
|
+
process.exitCode = 1, console.error(`Error: ${APP_NAME$1} UI frontend bundle not found. Reinstall or rebuild ${APP_NAME$1}. For dev-only overrides, set NEXTCLAW_UI_STATIC_DIR to a built frontend directory.`);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const healthUrl = `${apiUrl}/health`;
|
|
200
|
+
const portPreflight = await this.deps.checkUiPortPreflight({
|
|
201
|
+
host: uiConfig.host,
|
|
202
|
+
port: uiConfig.port,
|
|
203
|
+
healthUrl
|
|
204
|
+
});
|
|
205
|
+
if (!portPreflight.ok) {
|
|
206
|
+
process.exitCode = 1, console.error(`Error: Cannot start ${APP_NAME$1} because UI port ${uiConfig.port} is already occupied.`), console.error(portPreflight.message);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (portPreflight.reusedExistingHealthyTarget) {
|
|
210
|
+
await this.reuseExistingHealthyStartTarget({
|
|
211
|
+
uiConfig,
|
|
212
|
+
uiUrl,
|
|
213
|
+
apiUrl,
|
|
214
|
+
open
|
|
215
|
+
});
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
await this.startNewManagedServiceTarget({
|
|
219
|
+
config,
|
|
220
|
+
uiConfig,
|
|
221
|
+
uiUrl,
|
|
222
|
+
apiUrl,
|
|
223
|
+
healthUrl,
|
|
224
|
+
startupTimeoutMs
|
|
225
|
+
});
|
|
226
|
+
if (open) openBrowser(uiUrl);
|
|
227
|
+
};
|
|
228
|
+
stopService = async () => {
|
|
229
|
+
const state = managedServiceStateStore.read();
|
|
230
|
+
if (!state) {
|
|
231
|
+
console.log("No running background service found.");
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (!isProcessRunning(state.pid)) {
|
|
235
|
+
console.log("Service is not running. Cleaning up state.");
|
|
236
|
+
managedServiceStateStore.clear();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
console.log(`Stopping ${APP_NAME$1} (PID ${state.pid})...`);
|
|
240
|
+
try {
|
|
241
|
+
process.kill(state.pid, "SIGTERM");
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error(`Failed to stop service: ${String(error)}`);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (!await waitForExit(state.pid, 3e3)) {
|
|
247
|
+
try {
|
|
248
|
+
process.kill(state.pid, "SIGKILL");
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error(`Failed to force stop service: ${String(error)}`);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
await waitForExit(state.pid, 2e3);
|
|
254
|
+
}
|
|
255
|
+
managedServiceStateStore.clear();
|
|
256
|
+
localUiRuntimeStore.clearIfOwnedByProcess(state.pid);
|
|
257
|
+
console.log(`✓ ${APP_NAME$1} stopped`);
|
|
258
|
+
};
|
|
259
|
+
handleExistingManagedService = async (params) => {
|
|
260
|
+
const { existing, options, uiConfig } = params;
|
|
261
|
+
console.log(`✓ ${APP_NAME$1} is already running (PID ${existing.pid})`);
|
|
262
|
+
console.log(`UI: ${existing.uiUrl}`);
|
|
263
|
+
console.log(`API: ${existing.apiUrl}`);
|
|
264
|
+
const binding = resolveManagedServiceUiBinding(existing);
|
|
265
|
+
if (binding.host !== uiConfig.host || binding.port !== uiConfig.port) {
|
|
266
|
+
console.log(`Detected running service UI bind (${binding.host}:${binding.port}); enforcing (${uiConfig.host}:${uiConfig.port})...`);
|
|
267
|
+
await this.stopService();
|
|
268
|
+
const stateAfterStop = managedServiceStateStore.read();
|
|
269
|
+
if (stateAfterStop && isProcessRunning(stateAfterStop.pid)) {
|
|
270
|
+
process.exitCode = 1;
|
|
271
|
+
console.error("Error: Failed to stop running service while enforcing public UI exposure.");
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
await this.startService(options);
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
await this.deps.printPublicUiUrls(binding.host, binding.port);
|
|
278
|
+
console.log(`Logs: ${existing.logPath}`);
|
|
279
|
+
this.deps.printServiceControlHints();
|
|
280
|
+
return true;
|
|
281
|
+
};
|
|
282
|
+
reuseExistingHealthyStartTarget = async (params) => {
|
|
283
|
+
const { apiUrl, open, uiConfig, uiUrl } = params;
|
|
284
|
+
console.log(`✓ ${APP_NAME$1} is already serving the target UI/API port`);
|
|
285
|
+
console.log(`UI: ${uiUrl}`);
|
|
286
|
+
console.log(`API: ${apiUrl}`);
|
|
287
|
+
console.warn([
|
|
288
|
+
`Warning: The healthy listener on ${uiConfig.port} is not tracked by ${managedServiceStateStore.path}.`,
|
|
289
|
+
"This start call reused the existing runtime instead of spawning another one.",
|
|
290
|
+
"Use the owning process or port-level tools to stop it; managed stop/restart will not control it automatically."
|
|
291
|
+
].join(" "));
|
|
292
|
+
await this.deps.printPublicUiUrls(uiConfig.host, uiConfig.port);
|
|
293
|
+
if (open) openBrowser(uiUrl);
|
|
294
|
+
};
|
|
295
|
+
startNewManagedServiceTarget = async (params) => {
|
|
296
|
+
const { apiUrl, config, healthUrl, startupTimeoutMs, uiConfig, uiUrl } = params;
|
|
297
|
+
const startup = spawnManagedService({
|
|
298
|
+
appName: APP_NAME$1,
|
|
299
|
+
config,
|
|
300
|
+
uiConfig,
|
|
301
|
+
uiUrl,
|
|
302
|
+
apiUrl,
|
|
303
|
+
healthUrl,
|
|
304
|
+
startupTimeoutMs,
|
|
305
|
+
resolveStartupTimeoutMs: this.resolveStartupTimeoutMs,
|
|
306
|
+
appendStartupStage: this.appendStartupStage,
|
|
307
|
+
printStartupFailureDiagnostics: this.printStartupFailureDiagnostics,
|
|
308
|
+
resolveServiceLogPath
|
|
309
|
+
});
|
|
310
|
+
if (!startup) {
|
|
311
|
+
this.serviceLogger.fatal("managed service startup aborted", { reason: "child_process_not_created" });
|
|
312
|
+
process.exitCode = 1;
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const readiness = await waitForManagedServiceReadiness({
|
|
316
|
+
appName: APP_NAME$1,
|
|
317
|
+
childPid: startup.snapshot.pid,
|
|
318
|
+
healthUrl,
|
|
319
|
+
logPath: startup.logPath,
|
|
320
|
+
readinessTimeoutMs: startup.readinessTimeoutMs,
|
|
321
|
+
quickPhaseTimeoutMs: startup.quickPhaseTimeoutMs,
|
|
322
|
+
extendedPhaseTimeoutMs: startup.extendedPhaseTimeoutMs,
|
|
323
|
+
appendStartupStage: this.appendStartupStage,
|
|
324
|
+
waitForBackgroundServiceReady: this.waitForBackgroundServiceReady,
|
|
325
|
+
isProcessRunning
|
|
326
|
+
});
|
|
327
|
+
if (!readiness.ready && !isProcessRunning(startup.snapshot.pid)) {
|
|
328
|
+
process.exitCode = 1;
|
|
329
|
+
managedServiceStateStore.clear();
|
|
330
|
+
const hint = readiness.lastProbeError ? ` Last probe error: ${readiness.lastProbeError}` : "";
|
|
331
|
+
this.appendStartupStage(startup.logPath, `startup failed: process exited before ready.${hint}`);
|
|
332
|
+
this.serviceLogger.fatal("managed service exited before readiness completed", {
|
|
333
|
+
uiUrl,
|
|
334
|
+
apiUrl,
|
|
335
|
+
healthUrl,
|
|
336
|
+
logPath: startup.logPath,
|
|
337
|
+
...readiness.lastProbeError ? { lastProbeError: readiness.lastProbeError } : {}
|
|
338
|
+
});
|
|
339
|
+
console.error(`Error: Failed to start background service. Check logs: ${startup.logPath}.${hint}`);
|
|
340
|
+
this.printStartupFailureDiagnostics({
|
|
341
|
+
uiUrl,
|
|
342
|
+
apiUrl,
|
|
343
|
+
healthUrl,
|
|
344
|
+
logPath: startup.logPath,
|
|
345
|
+
lastProbeError: readiness.lastProbeError
|
|
346
|
+
});
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
startup.child.unref();
|
|
350
|
+
const readySnapshot = resolveManagedServiceReadySnapshot({ snapshot: startup.snapshot });
|
|
351
|
+
await reportManagedServiceStart({
|
|
352
|
+
appName: APP_NAME$1,
|
|
353
|
+
state: writeReadyManagedServiceState({
|
|
354
|
+
readinessTimeoutMs: startup.readinessTimeoutMs,
|
|
355
|
+
readiness,
|
|
356
|
+
snapshot: readySnapshot
|
|
357
|
+
}),
|
|
358
|
+
uiConfig,
|
|
359
|
+
uiUrl,
|
|
360
|
+
apiUrl,
|
|
361
|
+
readinessTimeoutMs: startup.readinessTimeoutMs,
|
|
362
|
+
readiness,
|
|
363
|
+
printPublicUiUrls: this.deps.printPublicUiUrls,
|
|
364
|
+
printServiceControlHints: this.deps.printServiceControlHints
|
|
365
|
+
});
|
|
366
|
+
};
|
|
367
|
+
waitForBackgroundServiceReady = async (params) => {
|
|
368
|
+
const { pid, healthUrl, timeoutMs } = params;
|
|
369
|
+
const startedAt = Date.now();
|
|
370
|
+
let lastProbeError = null;
|
|
371
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
372
|
+
if (!isProcessRunning(pid)) return {
|
|
373
|
+
ready: false,
|
|
374
|
+
lastProbeError
|
|
375
|
+
};
|
|
376
|
+
const probe = await probeHealthEndpoint(healthUrl);
|
|
377
|
+
if (!probe.healthy) {
|
|
378
|
+
lastProbeError = probe.error;
|
|
379
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 200));
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 300));
|
|
383
|
+
if (isProcessRunning(pid)) return {
|
|
384
|
+
ready: true,
|
|
385
|
+
lastProbeError: null
|
|
386
|
+
};
|
|
387
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 200));
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
ready: false,
|
|
391
|
+
lastProbeError
|
|
392
|
+
};
|
|
393
|
+
};
|
|
394
|
+
resolveStartupTimeoutMs = (overrideTimeoutMs) => {
|
|
395
|
+
const fallback = process.platform === "win32" ? 28e3 : 33e3;
|
|
396
|
+
const envRaw = process.env.NEXTCLAW_START_TIMEOUT_MS?.trim();
|
|
397
|
+
const envValue = envRaw ? Number(envRaw) : NaN;
|
|
398
|
+
const fromEnv = Number.isFinite(envValue) && envValue > 0 ? Math.floor(envValue) : null;
|
|
399
|
+
const resolved = (Number.isFinite(overrideTimeoutMs) && Number(overrideTimeoutMs) > 0 ? Math.floor(Number(overrideTimeoutMs)) : null) ?? fromEnv ?? fallback;
|
|
400
|
+
return Math.max(3e3, resolved);
|
|
401
|
+
};
|
|
402
|
+
appendStartupStage = (logPath, message) => {
|
|
403
|
+
try {
|
|
404
|
+
this.serviceLogger.child("startup").info(message, { logPath });
|
|
405
|
+
} catch (error) {
|
|
406
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
407
|
+
console.error(`Warning: failed to write startup diagnostics log (${logPath}): ${detail}`);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
printStartupFailureDiagnostics = (params) => {
|
|
411
|
+
const { apiUrl, healthUrl, lastProbeError, logPath, uiUrl } = params;
|
|
412
|
+
const statePath = managedServiceStateStore.path;
|
|
413
|
+
const lines = [
|
|
414
|
+
"Startup diagnostics:",
|
|
415
|
+
`- UI URL: ${uiUrl}`,
|
|
416
|
+
`- API URL: ${apiUrl}`,
|
|
417
|
+
`- Health probe: ${healthUrl}`,
|
|
418
|
+
`- Service state path: ${statePath}`,
|
|
419
|
+
`- Startup log path: ${logPath}`
|
|
420
|
+
];
|
|
421
|
+
if (lastProbeError) lines.push(`- Last probe detail: ${lastProbeError}`);
|
|
422
|
+
console.error(lines.join("\n"));
|
|
423
|
+
};
|
|
424
|
+
};
|
|
425
|
+
//#endregion
|
|
426
|
+
export { ManagedServiceCommandService, reportManagedServiceStart, resolveManagedServiceReadySnapshot, resolveManagedServiceUiBinding, resolveSessionRouteCandidate, spawnManagedService, waitForManagedServiceReadiness };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ManagedServiceState } from "../../stores/managed-service-state.store.js";
|
|
2
|
+
import { Config } from "@nextclaw/core";
|
|
3
|
+
import { RemoteServiceModule } from "@nextclaw/remote";
|
|
4
|
+
|
|
5
|
+
//#region src/shared/services/runtime/service-remote-runtime.service.d.ts
|
|
6
|
+
type ManagedServiceSnapshot = {
|
|
7
|
+
pid: number;
|
|
8
|
+
uiUrl: string;
|
|
9
|
+
apiUrl: string;
|
|
10
|
+
uiHost: string;
|
|
11
|
+
uiPort: number;
|
|
12
|
+
logPath: string;
|
|
13
|
+
};
|
|
14
|
+
type RemoteRuntimeOwnershipClaim = {
|
|
15
|
+
ok: true;
|
|
16
|
+
release: () => void;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
error: string;
|
|
20
|
+
};
|
|
21
|
+
declare function claimManagedRemoteRuntimeOwnership(params: {
|
|
22
|
+
localOrigin: string;
|
|
23
|
+
lockPath?: string;
|
|
24
|
+
currentPid?: number;
|
|
25
|
+
now?: () => string;
|
|
26
|
+
isProcessRunningFn?: (pid: number) => boolean;
|
|
27
|
+
readServiceStateFn?: () => ManagedServiceState | null;
|
|
28
|
+
}): RemoteRuntimeOwnershipClaim;
|
|
29
|
+
declare function createManagedRemoteModule(params: {
|
|
30
|
+
loadConfig: () => Config;
|
|
31
|
+
uiEnabled: boolean;
|
|
32
|
+
localOrigin: string;
|
|
33
|
+
}): RemoteServiceModule | null;
|
|
34
|
+
declare function createManagedRemoteModuleForUi(params: {
|
|
35
|
+
loadConfig: () => Config;
|
|
36
|
+
uiConfig: Pick<Config["ui"], "enabled" | "host" | "port">;
|
|
37
|
+
localOriginOverride?: string;
|
|
38
|
+
}): RemoteServiceModule | null;
|
|
39
|
+
declare function writeInitialManagedServiceState(params: {
|
|
40
|
+
config: Config;
|
|
41
|
+
readinessTimeoutMs: number;
|
|
42
|
+
snapshot: ManagedServiceSnapshot;
|
|
43
|
+
}): void;
|
|
44
|
+
declare function writeReadyManagedServiceState(params: {
|
|
45
|
+
readinessTimeoutMs: number;
|
|
46
|
+
readiness: {
|
|
47
|
+
ready: boolean;
|
|
48
|
+
lastProbeError: string | null;
|
|
49
|
+
};
|
|
50
|
+
snapshot: ManagedServiceSnapshot;
|
|
51
|
+
}): ManagedServiceState;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { claimManagedRemoteRuntimeOwnership, createManagedRemoteModule, createManagedRemoteModuleForUi, writeInitialManagedServiceState, writeReadyManagedServiceState };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { isProcessRunning, resolveUiApiBase } from "../../utils/cli.utils.js";
|
|
2
|
+
import { managedServiceStateStore } from "../../stores/managed-service-state.store.js";
|
|
3
|
+
import { buildNextclawConfiguredRemoteState, createNextclawRemoteConnector, createNextclawRemoteStatusStore } from "../../../commands/remote/services/remote-runtime-support.service.js";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
import { getDataDir } from "@nextclaw/core";
|
|
6
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { RemoteServiceModule } from "@nextclaw/remote";
|
|
8
|
+
//#region src/shared/services/runtime/service-remote-runtime.service.ts
|
|
9
|
+
function resolveRemoteOwnershipLockPath() {
|
|
10
|
+
return resolve(getDataDir(), "run", "remote-owner.lock.json");
|
|
11
|
+
}
|
|
12
|
+
function readRemoteOwnershipRecord(lockPath) {
|
|
13
|
+
try {
|
|
14
|
+
const raw = JSON.parse(readFileSync(lockPath, "utf-8"));
|
|
15
|
+
if (typeof raw.pid !== "number" || !Number.isFinite(raw.pid)) return null;
|
|
16
|
+
return {
|
|
17
|
+
pid: raw.pid,
|
|
18
|
+
localOrigin: typeof raw.localOrigin === "string" ? raw.localOrigin : "",
|
|
19
|
+
claimedAt: typeof raw.claimedAt === "string" ? raw.claimedAt : ""
|
|
20
|
+
};
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function removeRemoteOwnershipLock(lockPath) {
|
|
26
|
+
if (!existsSync(lockPath)) return;
|
|
27
|
+
try {
|
|
28
|
+
unlinkSync(lockPath);
|
|
29
|
+
} catch {
|
|
30
|
+
try {
|
|
31
|
+
rmSync(lockPath, { force: true });
|
|
32
|
+
} catch {}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function buildManagedServiceOwnershipError(params) {
|
|
36
|
+
return `Remote access is already owned by running NextClaw service PID ${params.pid}${params.ownerOrigin ? ` (${params.ownerOrigin})` : ""}. Stop that service or disable remote there before starting another process with the same NEXTCLAW_HOME.`;
|
|
37
|
+
}
|
|
38
|
+
function buildLocalProcessOwnershipError(record) {
|
|
39
|
+
const originText = record.localOrigin ? ` (${record.localOrigin})` : "";
|
|
40
|
+
return `Remote access is already owned by local NextClaw process PID ${record.pid}${originText}. Stop that process or use a different NEXTCLAW_HOME before starting another remote-enabled process.`;
|
|
41
|
+
}
|
|
42
|
+
function createRemoteOwnershipRelease(params) {
|
|
43
|
+
let released = false;
|
|
44
|
+
return () => {
|
|
45
|
+
if (released) return;
|
|
46
|
+
released = true;
|
|
47
|
+
const current = readRemoteOwnershipRecord(params.lockPath);
|
|
48
|
+
if (!current || current.pid === params.claim.pid && current.claimedAt === params.claim.claimedAt) removeRemoteOwnershipLock(params.lockPath);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function detectManagedRemoteOwnershipConflict(params) {
|
|
52
|
+
const runningService = params.readServiceStateFn();
|
|
53
|
+
if (!runningService || runningService.pid === params.currentPid || !params.isProcessRunningFn(runningService.pid) || !runningService.remote?.enabled) return null;
|
|
54
|
+
return buildManagedServiceOwnershipError({
|
|
55
|
+
pid: runningService.pid,
|
|
56
|
+
ownerOrigin: runningService.remote.localOrigin ?? runningService.uiUrl
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function tryClaimRemoteOwnershipLock(params) {
|
|
60
|
+
for (let attempt = 0; attempt < 2; attempt += 1) try {
|
|
61
|
+
const fd = openSync(params.lockPath, "wx");
|
|
62
|
+
writeFileSync(fd, `${JSON.stringify(params.claim, null, 2)}\n`, "utf-8");
|
|
63
|
+
closeSync(fd);
|
|
64
|
+
return {
|
|
65
|
+
ok: true,
|
|
66
|
+
release: createRemoteOwnershipRelease({
|
|
67
|
+
lockPath: params.lockPath,
|
|
68
|
+
claim: params.claim
|
|
69
|
+
})
|
|
70
|
+
};
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if ((typeof error === "object" && error && "code" in error ? String(error.code) : "") !== "EEXIST") return {
|
|
73
|
+
ok: false,
|
|
74
|
+
error: `Failed to claim local remote runtime ownership: ${error instanceof Error ? error.message : String(error)}`
|
|
75
|
+
};
|
|
76
|
+
const existing = readRemoteOwnershipRecord(params.lockPath);
|
|
77
|
+
if (existing && existing.pid !== params.currentPid && params.isProcessRunningFn(existing.pid)) return {
|
|
78
|
+
ok: false,
|
|
79
|
+
error: buildLocalProcessOwnershipError(existing)
|
|
80
|
+
};
|
|
81
|
+
removeRemoteOwnershipLock(params.lockPath);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
error: "Failed to claim local remote runtime ownership after clearing a stale lock."
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function claimManagedRemoteRuntimeOwnership(params) {
|
|
89
|
+
const lockPath = params.lockPath ?? resolveRemoteOwnershipLockPath();
|
|
90
|
+
const currentPid = params.currentPid ?? process.pid;
|
|
91
|
+
const now = params.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
92
|
+
const isProcessRunningFn = params.isProcessRunningFn ?? isProcessRunning;
|
|
93
|
+
const managedConflict = detectManagedRemoteOwnershipConflict({
|
|
94
|
+
currentPid,
|
|
95
|
+
isProcessRunningFn,
|
|
96
|
+
readServiceStateFn: params.readServiceStateFn ?? managedServiceStateStore.read
|
|
97
|
+
});
|
|
98
|
+
if (managedConflict) return {
|
|
99
|
+
ok: false,
|
|
100
|
+
error: managedConflict
|
|
101
|
+
};
|
|
102
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
103
|
+
return tryClaimRemoteOwnershipLock({
|
|
104
|
+
lockPath,
|
|
105
|
+
claim: {
|
|
106
|
+
pid: currentPid,
|
|
107
|
+
localOrigin: params.localOrigin,
|
|
108
|
+
claimedAt: now()
|
|
109
|
+
},
|
|
110
|
+
currentPid,
|
|
111
|
+
isProcessRunningFn
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function createManagedRemoteModule(params) {
|
|
115
|
+
if (!params.uiEnabled) return null;
|
|
116
|
+
return new RemoteServiceModule({
|
|
117
|
+
loadConfig: params.loadConfig,
|
|
118
|
+
uiEnabled: params.uiEnabled,
|
|
119
|
+
localOrigin: params.localOrigin,
|
|
120
|
+
statusStore: createNextclawRemoteStatusStore("service"),
|
|
121
|
+
createConnector: (logger) => createNextclawRemoteConnector({ logger }),
|
|
122
|
+
claimOwnership: () => claimManagedRemoteRuntimeOwnership({ localOrigin: params.localOrigin }),
|
|
123
|
+
logger: {
|
|
124
|
+
info: (message) => console.log(`[remote] ${message}`),
|
|
125
|
+
warn: (message) => console.warn(`[remote] ${message}`),
|
|
126
|
+
error: (message) => console.error(`[remote] ${message}`)
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function createManagedRemoteModuleForUi(params) {
|
|
131
|
+
const explicitLocalOrigin = params.localOriginOverride?.trim() ?? process.env.NEXTCLAW_REMOTE_LOCAL_ORIGIN?.trim();
|
|
132
|
+
return createManagedRemoteModule({
|
|
133
|
+
loadConfig: params.loadConfig,
|
|
134
|
+
uiEnabled: params.uiConfig.enabled,
|
|
135
|
+
localOrigin: explicitLocalOrigin && explicitLocalOrigin.length > 0 ? explicitLocalOrigin.replace(/\/+$/, "") : resolveUiApiBase(params.uiConfig.host, params.uiConfig.port)
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function writeInitialManagedServiceState(params) {
|
|
139
|
+
managedServiceStateStore.write({
|
|
140
|
+
pid: params.snapshot.pid,
|
|
141
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
142
|
+
uiUrl: params.snapshot.uiUrl,
|
|
143
|
+
apiUrl: params.snapshot.apiUrl,
|
|
144
|
+
uiHost: params.snapshot.uiHost,
|
|
145
|
+
uiPort: params.snapshot.uiPort,
|
|
146
|
+
logPath: params.snapshot.logPath,
|
|
147
|
+
startupLastProbeError: null,
|
|
148
|
+
startupTimeoutMs: params.readinessTimeoutMs,
|
|
149
|
+
startupCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
150
|
+
...params.config.remote.enabled ? { remote: buildNextclawConfiguredRemoteState(params.config) } : {}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function writeReadyManagedServiceState(params) {
|
|
154
|
+
const currentState = managedServiceStateStore.read();
|
|
155
|
+
const state = {
|
|
156
|
+
pid: params.snapshot.pid,
|
|
157
|
+
startedAt: currentState?.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
158
|
+
uiUrl: params.snapshot.uiUrl,
|
|
159
|
+
apiUrl: params.snapshot.apiUrl,
|
|
160
|
+
uiHost: params.snapshot.uiHost,
|
|
161
|
+
uiPort: params.snapshot.uiPort,
|
|
162
|
+
logPath: params.snapshot.logPath,
|
|
163
|
+
startupState: params.readiness.ready ? "ready" : "degraded",
|
|
164
|
+
startupLastProbeError: params.readiness.lastProbeError,
|
|
165
|
+
startupTimeoutMs: params.readinessTimeoutMs,
|
|
166
|
+
startupCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
167
|
+
...currentState?.remote ? { remote: currentState.remote } : {}
|
|
168
|
+
};
|
|
169
|
+
managedServiceStateStore.write(state);
|
|
170
|
+
return state;
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
export { claimManagedRemoteRuntimeOwnership, createManagedRemoteModule, createManagedRemoteModuleForUi, writeInitialManagedServiceState, writeReadyManagedServiceState };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/shared/services/runtime/utils/skills-loader.utils.d.ts
|
|
2
|
+
type SkillInfo = {
|
|
3
|
+
name: string;
|
|
4
|
+
path: string;
|
|
5
|
+
source: "workspace" | "builtin";
|
|
6
|
+
};
|
|
7
|
+
type SkillsLoaderInstance = {
|
|
8
|
+
listSkills: (filterUnavailable?: boolean) => SkillInfo[];
|
|
9
|
+
};
|
|
10
|
+
declare function createSkillsLoader(workspace: string): SkillsLoaderInstance | null;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { createSkillsLoader };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as NextclawCore from "@nextclaw/core";
|
|
2
|
+
//#region src/shared/services/runtime/utils/skills-loader.utils.ts
|
|
3
|
+
function createSkillsLoader(workspace) {
|
|
4
|
+
const ctor = NextclawCore.SkillsLoader;
|
|
5
|
+
if (!ctor) return null;
|
|
6
|
+
return new ctor(workspace);
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
export { createSkillsLoader };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { UiNcpAgent } from "@nextclaw/server";
|
|
2
|
+
import { AgentRuntimeHandle } from "@nextclaw/kernel";
|
|
3
|
+
|
|
4
|
+
//#region src/shared/services/session/service-deferred-ncp-agent.service.d.ts
|
|
5
|
+
type DeferredUiNcpAgentController = {
|
|
6
|
+
agent: UiNcpAgent;
|
|
7
|
+
activate: (agent: AgentRuntimeHandle) => void;
|
|
8
|
+
clear: () => void;
|
|
9
|
+
close: () => Promise<void>;
|
|
10
|
+
isReady: () => boolean;
|
|
11
|
+
};
|
|
12
|
+
declare function createDeferredUiNcpAgent(basePath?: string): DeferredUiNcpAgentController;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { DeferredUiNcpAgentController, createDeferredUiNcpAgent };
|