@nextclaw/service 0.2.15-beta.0 → 0.2.15-beta.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/dist/managers/managed-service.manager.d.ts.map +1 -1
- package/dist/managers/managed-service.manager.js +9 -8
- package/dist/managers/managed-service.manager.js.map +1 -1
- package/dist/managers/service-gateway.manager.d.ts +1 -1
- package/dist/managers/service-gateway.manager.d.ts.map +1 -1
- package/dist/managers/service-gateway.manager.js +2 -2
- package/dist/managers/service-gateway.manager.js.map +1 -1
- package/dist/services/runtime/managed-service-supervisor.service.d.ts +15 -1
- package/dist/services/runtime/managed-service-supervisor.service.d.ts.map +1 -1
- package/dist/services/runtime/managed-service-supervisor.service.js +42 -18
- package/dist/services/runtime/managed-service-supervisor.service.js.map +1 -1
- package/package.json +26 -26
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"managed-service.manager.d.ts","names":[],"sources":["../../src/managers/managed-service.manager.ts"],"mappings":";;;;;;;KAiBK,QAAA,GAAS,YAAA,CAAa,MAAA;AAAA,cAEd,qBAAA;EAAA,QAaS,IAAA;EAAA,QAZZ,gBAAA;EAAA,QACA,2BAAA;EAAA,iBACS,aAAA;EAAA,iBACA,wBAAA;EAAA,iBACA,4BAAA;cAQG,IAAA;IAClB,cAAA,GAAiB,MAAA,EAAQ,oBAAA,KAAyB,OAAA;IAClD,4BAAA,GAA+B,aAAA;EAAA;EAGjC,YAAA,GAAsB,OAAA;IAAW,WAAA,GAAc,OAAA,CAAQ,QAAA;IAAe,WAAA;EAAA,MAAqC,OAAA;
|
|
1
|
+
{"version":3,"file":"managed-service.manager.d.ts","names":[],"sources":["../../src/managers/managed-service.manager.ts"],"mappings":";;;;;;;KAiBK,QAAA,GAAS,YAAA,CAAa,MAAA;AAAA,cAEd,qBAAA;EAAA,QAaS,IAAA;EAAA,QAZZ,gBAAA;EAAA,QACA,2BAAA;EAAA,iBACS,aAAA;EAAA,iBACA,wBAAA;EAAA,iBACA,4BAAA;cAQG,IAAA;IAClB,cAAA,GAAiB,MAAA,EAAQ,oBAAA,KAAyB,OAAA;IAClD,4BAAA,GAA+B,aAAA;EAAA;EAGjC,YAAA,GAAsB,OAAA;IAAW,WAAA,GAAc,OAAA,CAAQ,QAAA;IAAe,WAAA;EAAA,MAAqC,OAAA;EA6B3G,YAAA,GAAsB,OAAA,EAAS,mBAAA,KAAsB,OAAA;EAIrD,WAAA,QAAwB,OAAA;EAIxB,aAAA,GAAuB,OAAA;IACrB,WAAA,EAAa,OAAA,CAAQ,QAAA;IACrB,IAAA;EAAA,MACE,OAAA;EAAA,QAII,8BAAA;EAAA,QAUA,kBAAA;EAAA,QAIA,gBAAA;EAAA,QAWA,UAAA;EAAA,QA2CA,6BAAA;EAAA,QAWA,yBAAA;EAAA,QAcA,oBAAA;EAAA,QA6BA,iBAAA;EAAA,QAoBA,wBAAA;AAAA"}
|
|
@@ -29,20 +29,21 @@ var ManagedServiceManager = class {
|
|
|
29
29
|
startGateway = async (options = {}) => {
|
|
30
30
|
this.ensureRuntimeLoggingInstalled();
|
|
31
31
|
this.installProcessExitLogging();
|
|
32
|
-
|
|
33
|
-
this.runtimeLogger.info("runtime.process.started", {
|
|
34
|
-
runtimeKind: "serve-process",
|
|
35
|
-
pid: process.pid,
|
|
36
|
-
source: "ManagedServiceManager.startGateway"
|
|
37
|
-
});
|
|
38
|
-
await new ServiceGatewayManager({
|
|
32
|
+
const gateway = new ServiceGatewayManager({
|
|
39
33
|
requestRestart: this.deps.requestRestart,
|
|
40
34
|
initializeAgentHomeDirectory: this.deps.initializeAgentHomeDirectory,
|
|
41
35
|
startService: this.startService,
|
|
42
36
|
stopService: this.stopService,
|
|
43
37
|
runCliSubcommand: this.runCliSubcommand,
|
|
44
38
|
installBuiltinMarketplaceSkill: this.installBuiltinMarketplaceSkill
|
|
45
|
-
}, { ...options })
|
|
39
|
+
}, { ...options });
|
|
40
|
+
this.managedServiceSupervisor.installCurrentProcessLifecycleTracking({ onSignal: async () => await gateway.stop() });
|
|
41
|
+
this.runtimeLogger.info("runtime.process.started", {
|
|
42
|
+
runtimeKind: "serve-process",
|
|
43
|
+
pid: process.pid,
|
|
44
|
+
source: "ManagedServiceManager.startGateway"
|
|
45
|
+
});
|
|
46
|
+
await gateway.start();
|
|
46
47
|
this.runtimeLogger.info("runtime.process.ready", {
|
|
47
48
|
runtimeKind: "serve-process",
|
|
48
49
|
pid: process.pid,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"managed-service.manager.js","names":["getWorkspacePath","loadConfig"],"sources":["../../src/managers/managed-service.manager.ts"],"sourcesContent":["import * as NextclawCore from \"@nextclaw/core\";\nimport { spawn } from \"node:child_process\";\nimport { SkillManager } from \"@nextclaw/kernel\";\nimport type { RequestRestartParams } from \"@nextclaw-service/types/cli.types.js\";\nimport { ManagedServiceCommandService, type StartServiceOptions } from \"@nextclaw-service/services/runtime/service-managed-startup.service.js\";\nimport { ManagedServiceSupervisor } from \"@nextclaw-service/services/runtime/managed-service-supervisor.service.js\";\nimport { ServiceGatewayManager } from \"@nextclaw-service/managers/service-gateway.manager.js\";\nimport { NextclawDistributionService } from \"@nextclaw-service/services/runtime/nextclaw-distribution.service.js\";\nimport { describeUnmanagedHealthyTargetMessage, inspectUiTarget } from \"@nextclaw-service/utils/service-port-probe.utils.js\";\nimport { resolveCliSubcommandEntry } from \"@nextclaw-service/utils/marketplace/cli-subcommand-launch.utils.js\";\nimport { isLoopbackHost, resolvePublicIp, resolveUiStaticDir } from \"@nextclaw-service/utils/cli.utils.js\";\nexport { buildMarketplaceSkillInstallArgs, pickUserFacingCommandSummary } from \"@nextclaw-service/utils/marketplace/service-marketplace-helpers.utils.js\";\nexport { describeUnmanagedHealthyTargetMessage };\nconst {\n getWorkspacePath,\n loadConfig,\n} = NextclawCore;\ntype Config = NextclawCore.Config;\n\nexport class ManagedServiceManager {\n private loggingInstalled = false;\n private processExitLoggingInstalled = false;\n private readonly runtimeLogger = NextclawCore.getAppLogger(\"service.runtime\");\n private readonly managedServiceSupervisor = new ManagedServiceSupervisor();\n private readonly managedServiceCommandService = new ManagedServiceCommandService({\n startGateway: async (options) => await this.startGateway(options),\n printPublicUiUrls: async (host, port) => await this.printPublicUiUrls(host, port),\n printServiceControlHints: () => this.printServiceControlHints(),\n checkUiPortPreflight: async (params) => await this.checkUiPortPreflight(params),\n resolveUiStaticDir: () => resolveUiStaticDir(NextclawDistributionService.get().uiDistDir)\n });\n\n constructor(private deps: {\n requestRestart: (params: RequestRestartParams) => Promise<void>;\n initializeAgentHomeDirectory: (homeDirectory: string) => void;\n }) {}\n\n startGateway = async (options: { uiOverrides?: Partial<Config[\"ui\"]>; uiStaticDir?: string | null } = {}): Promise<void> => {\n this.ensureRuntimeLoggingInstalled();\n this.installProcessExitLogging();\n this.managedServiceSupervisor.installCurrentProcessLifecycleTracking();\n this.runtimeLogger.info(\"runtime.process.started\", {\n runtimeKind: \"serve-process\",\n pid: process.pid,\n source: \"ManagedServiceManager.startGateway\"\n });\n await new ServiceGatewayManager({\n requestRestart: this.deps.requestRestart,\n initializeAgentHomeDirectory: this.deps.initializeAgentHomeDirectory,\n startService: this.startService,\n stopService: this.stopService,\n runCliSubcommand: this.runCliSubcommand,\n installBuiltinMarketplaceSkill: this.installBuiltinMarketplaceSkill,\n }, {\n ...options\n }).start();\n this.runtimeLogger.info(\"runtime.process.ready\", {\n runtimeKind: \"serve-process\",\n pid: process.pid,\n source: \"ManagedServiceManager.startGateway\"\n });\n };\n\n startService = async (options: StartServiceOptions): Promise<void> => {\n await this.managedServiceCommandService.startService(options);\n };\n\n stopService = async (): Promise<void> => {\n await this.managedServiceCommandService.stopService();\n };\n\n runForeground = async (options: {\n uiOverrides: Partial<Config[\"ui\"]>;\n open: boolean;\n }): Promise<void> => {\n await this.managedServiceCommandService.runForeground(options);\n };\n\n private installBuiltinMarketplaceSkill = (slug: string, _force: boolean | undefined): { message: string; output?: string } | null => {\n const workspace = getWorkspacePath(loadConfig().agents.defaults.workspace);\n if (!new SkillManager({ workspace }).findBuiltinSkill(slug)) {\n return null;\n }\n return {\n message: `${slug} is already available (built-in)`\n };\n };\n\n private mergeCommandOutput = (stdout: string, stderr: string): string => {\n return `${stdout}\\n${stderr}`.trim();\n };\n\n private runCliSubcommand = (args: string[], timeoutMs = 180_000): Promise<string> => {\n const cliEntry = resolveCliSubcommandEntry({\n argvEntry: process.argv[1],\n importMetaUrl: import.meta.url\n });\n return this.runCommand(process.execPath, [...process.execArgv, cliEntry, ...args], {\n cwd: process.cwd(),\n timeoutMs\n }).then((result) => this.mergeCommandOutput(result.stdout, result.stderr));\n };\n\n private runCommand = (command: string, args: string[], options: { cwd?: string; timeoutMs?: number } = {}): Promise<{ stdout: string; stderr: string }> => {\n const timeoutMs = options.timeoutMs ?? 180_000;\n return new Promise((resolvePromise, rejectPromise) => {\n const child = spawn(command, args, {\n cwd: options.cwd ?? process.cwd(),\n env: process.env,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n windowsHide: true\n });\n\n let stdout = \"\";\n let stderr = \"\";\n child.stdout?.setEncoding(\"utf-8\");\n child.stderr?.setEncoding(\"utf-8\");\n child.stdout?.on(\"data\", (chunk: string) => {\n stdout += chunk;\n });\n child.stderr?.on(\"data\", (chunk: string) => {\n stderr += chunk;\n });\n\n const timer = setTimeout(() => {\n child.kill(\"SIGTERM\");\n rejectPromise(new Error(`command timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n child.on(\"error\", (error) => {\n clearTimeout(timer);\n rejectPromise(new Error(`failed to start command: ${String(error)}`));\n });\n\n child.on(\"close\", (code) => {\n clearTimeout(timer);\n const output = this.mergeCommandOutput(stdout, stderr);\n if (code === 0) {\n resolvePromise({ stdout, stderr });\n return;\n }\n rejectPromise(new Error(output || `command failed with code ${code ?? 1}`));\n });\n });\n };\n\n private ensureRuntimeLoggingInstalled = (): void => {\n if (this.loggingInstalled) {\n return;\n }\n NextclawCore.configureAppLogging({\n installConsoleMirror: true,\n installProcessCrashMonitor: true\n });\n this.loggingInstalled = true;\n };\n\n private installProcessExitLogging = (): void => {\n if (this.processExitLoggingInstalled) {\n return;\n }\n this.processExitLoggingInstalled = true;\n process.once(\"exit\", (code) => {\n this.runtimeLogger.warn(\"runtime.process.exited\", {\n runtimeKind: \"serve-process\",\n pid: process.pid,\n code\n });\n });\n };\n\n private checkUiPortPreflight = async (params: {\n host: string;\n port: number;\n healthUrl: string;\n }): Promise<\n | { ok: true; reusedExistingHealthyTarget: boolean }\n | { ok: false; message: string }\n > => {\n const target = await inspectUiTarget(params);\n if (target.state === \"available\") {\n return { ok: true, reusedExistingHealthyTarget: false };\n }\n if (target.state === \"healthy-existing\") {\n return { ok: true, reusedExistingHealthyTarget: true };\n }\n\n const lines = [`Port probe: ${target.availabilityDetail}`];\n if (target.probeError) {\n lines.push(`Health probe: ${target.probeError}`);\n }\n lines.push(\"The port is occupied by a process that does not answer as a healthy NextClaw HTTP server.\");\n lines.push(`Fix: free port ${params.port} or start NextClaw with another port via --ui-port <port>.`);\n lines.push(`Inspect locally with: ss -ltnp | grep ${params.port} || lsof -iTCP:${params.port} -sTCP:LISTEN -n -P`);\n return {\n ok: false,\n message: lines.join(\"\\n\")\n };\n };\n\n private printPublicUiUrls = async (host: string, port: number): Promise<void> => {\n if (isLoopbackHost(host)) {\n console.log(\"Public URL: disabled (UI host is loopback). Current release expects public exposure; run nextclaw restart.\");\n return;\n }\n\n const publicIp = await resolvePublicIp();\n if (!publicIp) {\n console.log(\"Public URL: UI is exposed, but automatic public IP detection failed.\");\n return;\n }\n\n const publicBase = `http://${publicIp}:${port}`;\n console.log(`Public UI (if firewall/NAT allows): ${publicBase}`);\n console.log(`Public API (if firewall/NAT allows): ${publicBase}/api`);\n console.log(`Public deploy note: NextClaw serves plain HTTP on ${port}.`);\n console.log(`For https:// or standard 80/443 access, terminate TLS in Nginx/Caddy and proxy to http://127.0.0.1:${port}.`);\n console.log(`If a reverse proxy returns 502, verify its upstream is http://127.0.0.1:${port} (not https://, not a stale port, and not a stopped process).`);\n };\n\n private printServiceControlHints = (): void => {\n console.log(\"Service controls:\");\n console.log(\" - Check status: NextClaw status\");\n console.log(\" - If you need to stop the service, run: NextClaw stop\");\n console.log(\" - View log paths: NextClaw logs path\");\n console.log(\" - Tail recent logs: NextClaw logs tail\");\n console.log(\" - Check autostart: NextClaw service autostart status --user\");\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"managed-service.manager.js","names":["getWorkspacePath","loadConfig"],"sources":["../../src/managers/managed-service.manager.ts"],"sourcesContent":["import * as NextclawCore from \"@nextclaw/core\";\nimport { spawn } from \"node:child_process\";\nimport { SkillManager } from \"@nextclaw/kernel\";\nimport type { RequestRestartParams } from \"@nextclaw-service/types/cli.types.js\";\nimport { ManagedServiceCommandService, type StartServiceOptions } from \"@nextclaw-service/services/runtime/service-managed-startup.service.js\";\nimport { ManagedServiceSupervisor } from \"@nextclaw-service/services/runtime/managed-service-supervisor.service.js\";\nimport { ServiceGatewayManager } from \"@nextclaw-service/managers/service-gateway.manager.js\";\nimport { NextclawDistributionService } from \"@nextclaw-service/services/runtime/nextclaw-distribution.service.js\";\nimport { describeUnmanagedHealthyTargetMessage, inspectUiTarget } from \"@nextclaw-service/utils/service-port-probe.utils.js\";\nimport { resolveCliSubcommandEntry } from \"@nextclaw-service/utils/marketplace/cli-subcommand-launch.utils.js\";\nimport { isLoopbackHost, resolvePublicIp, resolveUiStaticDir } from \"@nextclaw-service/utils/cli.utils.js\";\nexport { buildMarketplaceSkillInstallArgs, pickUserFacingCommandSummary } from \"@nextclaw-service/utils/marketplace/service-marketplace-helpers.utils.js\";\nexport { describeUnmanagedHealthyTargetMessage };\nconst {\n getWorkspacePath,\n loadConfig,\n} = NextclawCore;\ntype Config = NextclawCore.Config;\n\nexport class ManagedServiceManager {\n private loggingInstalled = false;\n private processExitLoggingInstalled = false;\n private readonly runtimeLogger = NextclawCore.getAppLogger(\"service.runtime\");\n private readonly managedServiceSupervisor = new ManagedServiceSupervisor();\n private readonly managedServiceCommandService = new ManagedServiceCommandService({\n startGateway: async (options) => await this.startGateway(options),\n printPublicUiUrls: async (host, port) => await this.printPublicUiUrls(host, port),\n printServiceControlHints: () => this.printServiceControlHints(),\n checkUiPortPreflight: async (params) => await this.checkUiPortPreflight(params),\n resolveUiStaticDir: () => resolveUiStaticDir(NextclawDistributionService.get().uiDistDir)\n });\n\n constructor(private deps: {\n requestRestart: (params: RequestRestartParams) => Promise<void>;\n initializeAgentHomeDirectory: (homeDirectory: string) => void;\n }) {}\n\n startGateway = async (options: { uiOverrides?: Partial<Config[\"ui\"]>; uiStaticDir?: string | null } = {}): Promise<void> => {\n this.ensureRuntimeLoggingInstalled();\n this.installProcessExitLogging();\n const gateway = new ServiceGatewayManager({\n requestRestart: this.deps.requestRestart,\n initializeAgentHomeDirectory: this.deps.initializeAgentHomeDirectory,\n startService: this.startService,\n stopService: this.stopService,\n runCliSubcommand: this.runCliSubcommand,\n installBuiltinMarketplaceSkill: this.installBuiltinMarketplaceSkill,\n }, {\n ...options\n });\n this.managedServiceSupervisor.installCurrentProcessLifecycleTracking({\n onSignal: async () => await gateway.stop(),\n });\n this.runtimeLogger.info(\"runtime.process.started\", {\n runtimeKind: \"serve-process\",\n pid: process.pid,\n source: \"ManagedServiceManager.startGateway\"\n });\n await gateway.start();\n this.runtimeLogger.info(\"runtime.process.ready\", {\n runtimeKind: \"serve-process\",\n pid: process.pid,\n source: \"ManagedServiceManager.startGateway\"\n });\n };\n\n startService = async (options: StartServiceOptions): Promise<void> => {\n await this.managedServiceCommandService.startService(options);\n };\n\n stopService = async (): Promise<void> => {\n await this.managedServiceCommandService.stopService();\n };\n\n runForeground = async (options: {\n uiOverrides: Partial<Config[\"ui\"]>;\n open: boolean;\n }): Promise<void> => {\n await this.managedServiceCommandService.runForeground(options);\n };\n\n private installBuiltinMarketplaceSkill = (slug: string, _force: boolean | undefined): { message: string; output?: string } | null => {\n const workspace = getWorkspacePath(loadConfig().agents.defaults.workspace);\n if (!new SkillManager({ workspace }).findBuiltinSkill(slug)) {\n return null;\n }\n return {\n message: `${slug} is already available (built-in)`\n };\n };\n\n private mergeCommandOutput = (stdout: string, stderr: string): string => {\n return `${stdout}\\n${stderr}`.trim();\n };\n\n private runCliSubcommand = (args: string[], timeoutMs = 180_000): Promise<string> => {\n const cliEntry = resolveCliSubcommandEntry({\n argvEntry: process.argv[1],\n importMetaUrl: import.meta.url\n });\n return this.runCommand(process.execPath, [...process.execArgv, cliEntry, ...args], {\n cwd: process.cwd(),\n timeoutMs\n }).then((result) => this.mergeCommandOutput(result.stdout, result.stderr));\n };\n\n private runCommand = (command: string, args: string[], options: { cwd?: string; timeoutMs?: number } = {}): Promise<{ stdout: string; stderr: string }> => {\n const timeoutMs = options.timeoutMs ?? 180_000;\n return new Promise((resolvePromise, rejectPromise) => {\n const child = spawn(command, args, {\n cwd: options.cwd ?? process.cwd(),\n env: process.env,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n windowsHide: true\n });\n\n let stdout = \"\";\n let stderr = \"\";\n child.stdout?.setEncoding(\"utf-8\");\n child.stderr?.setEncoding(\"utf-8\");\n child.stdout?.on(\"data\", (chunk: string) => {\n stdout += chunk;\n });\n child.stderr?.on(\"data\", (chunk: string) => {\n stderr += chunk;\n });\n\n const timer = setTimeout(() => {\n child.kill(\"SIGTERM\");\n rejectPromise(new Error(`command timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n child.on(\"error\", (error) => {\n clearTimeout(timer);\n rejectPromise(new Error(`failed to start command: ${String(error)}`));\n });\n\n child.on(\"close\", (code) => {\n clearTimeout(timer);\n const output = this.mergeCommandOutput(stdout, stderr);\n if (code === 0) {\n resolvePromise({ stdout, stderr });\n return;\n }\n rejectPromise(new Error(output || `command failed with code ${code ?? 1}`));\n });\n });\n };\n\n private ensureRuntimeLoggingInstalled = (): void => {\n if (this.loggingInstalled) {\n return;\n }\n NextclawCore.configureAppLogging({\n installConsoleMirror: true,\n installProcessCrashMonitor: true\n });\n this.loggingInstalled = true;\n };\n\n private installProcessExitLogging = (): void => {\n if (this.processExitLoggingInstalled) {\n return;\n }\n this.processExitLoggingInstalled = true;\n process.once(\"exit\", (code) => {\n this.runtimeLogger.warn(\"runtime.process.exited\", {\n runtimeKind: \"serve-process\",\n pid: process.pid,\n code\n });\n });\n };\n\n private checkUiPortPreflight = async (params: {\n host: string;\n port: number;\n healthUrl: string;\n }): Promise<\n | { ok: true; reusedExistingHealthyTarget: boolean }\n | { ok: false; message: string }\n > => {\n const target = await inspectUiTarget(params);\n if (target.state === \"available\") {\n return { ok: true, reusedExistingHealthyTarget: false };\n }\n if (target.state === \"healthy-existing\") {\n return { ok: true, reusedExistingHealthyTarget: true };\n }\n\n const lines = [`Port probe: ${target.availabilityDetail}`];\n if (target.probeError) {\n lines.push(`Health probe: ${target.probeError}`);\n }\n lines.push(\"The port is occupied by a process that does not answer as a healthy NextClaw HTTP server.\");\n lines.push(`Fix: free port ${params.port} or start NextClaw with another port via --ui-port <port>.`);\n lines.push(`Inspect locally with: ss -ltnp | grep ${params.port} || lsof -iTCP:${params.port} -sTCP:LISTEN -n -P`);\n return {\n ok: false,\n message: lines.join(\"\\n\")\n };\n };\n\n private printPublicUiUrls = async (host: string, port: number): Promise<void> => {\n if (isLoopbackHost(host)) {\n console.log(\"Public URL: disabled (UI host is loopback). Current release expects public exposure; run nextclaw restart.\");\n return;\n }\n\n const publicIp = await resolvePublicIp();\n if (!publicIp) {\n console.log(\"Public URL: UI is exposed, but automatic public IP detection failed.\");\n return;\n }\n\n const publicBase = `http://${publicIp}:${port}`;\n console.log(`Public UI (if firewall/NAT allows): ${publicBase}`);\n console.log(`Public API (if firewall/NAT allows): ${publicBase}/api`);\n console.log(`Public deploy note: NextClaw serves plain HTTP on ${port}.`);\n console.log(`For https:// or standard 80/443 access, terminate TLS in Nginx/Caddy and proxy to http://127.0.0.1:${port}.`);\n console.log(`If a reverse proxy returns 502, verify its upstream is http://127.0.0.1:${port} (not https://, not a stale port, and not a stopped process).`);\n };\n\n private printServiceControlHints = (): void => {\n console.log(\"Service controls:\");\n console.log(\" - Check status: NextClaw status\");\n console.log(\" - If you need to stop the service, run: NextClaw stop\");\n console.log(\" - View log paths: NextClaw logs path\");\n console.log(\" - Tail recent logs: NextClaw logs tail\");\n console.log(\" - Check autostart: NextClaw service autostart status --user\");\n };\n}\n"],"mappings":";;;;;;;;;;;;AAaA,MAAM,EACJ,kBAAA,oBACA,YAAA,iBACE;AAGJ,IAAa,wBAAb,MAAmC;CACjC,mBAA2B;CAC3B,8BAAsC;CACtC,gBAAiC,aAAa,aAAa,kBAAkB;CAC7E,2BAA4C,IAAI,0BAA0B;CAC1E,+BAAgD,IAAI,6BAA6B;EAC/E,cAAc,OAAO,YAAY,MAAM,KAAK,aAAa,QAAQ;EACjE,mBAAmB,OAAO,MAAM,SAAS,MAAM,KAAK,kBAAkB,MAAM,KAAK;EACjF,gCAAgC,KAAK,0BAA0B;EAC/D,sBAAsB,OAAO,WAAW,MAAM,KAAK,qBAAqB,OAAO;EAC/E,0BAA0B,mBAAmB,4BAA4B,KAAK,CAAC,UAAU;EAC1F,CAAC;CAEF,YAAY,MAGT;AAHiB,OAAA,OAAA;;CAKpB,eAAe,OAAO,UAAgF,EAAE,KAAoB;AAC1H,OAAK,+BAA+B;AACpC,OAAK,2BAA2B;EAChC,MAAM,UAAU,IAAI,sBAAsB;GACxC,gBAAgB,KAAK,KAAK;GAC1B,8BAA8B,KAAK,KAAK;GACxC,cAAc,KAAK;GACnB,aAAa,KAAK;GAClB,kBAAkB,KAAK;GACvB,gCAAgC,KAAK;GACtC,EAAE,EACD,GAAG,SACJ,CAAC;AACF,OAAK,yBAAyB,uCAAuC,EACnE,UAAU,YAAY,MAAM,QAAQ,MAAM,EAC3C,CAAC;AACF,OAAK,cAAc,KAAK,2BAA2B;GACjD,aAAa;GACb,KAAK,QAAQ;GACb,QAAQ;GACT,CAAC;AACF,QAAM,QAAQ,OAAO;AACrB,OAAK,cAAc,KAAK,yBAAyB;GAC/C,aAAa;GACb,KAAK,QAAQ;GACb,QAAQ;GACT,CAAC;;CAGJ,eAAe,OAAO,YAAgD;AACpE,QAAM,KAAK,6BAA6B,aAAa,QAAQ;;CAG/D,cAAc,YAA2B;AACvC,QAAM,KAAK,6BAA6B,aAAa;;CAGvD,gBAAgB,OAAO,YAGF;AACnB,QAAM,KAAK,6BAA6B,cAAc,QAAQ;;CAGhE,kCAA0C,MAAc,WAA6E;AAEnI,MAAI,CAAC,IAAI,aAAa,EAAE,WADNA,mBAAiBC,cAAY,CAAC,OAAO,SAAS,UAAU,EACvC,CAAC,CAAC,iBAAiB,KAAK,CACzD,QAAO;AAET,SAAO,EACL,SAAS,GAAG,KAAK,mCAClB;;CAGH,sBAA8B,QAAgB,WAA2B;AACvE,SAAO,GAAG,OAAO,IAAI,SAAS,MAAM;;CAGtC,oBAA4B,MAAgB,YAAY,SAA6B;EACnF,MAAM,WAAW,0BAA0B;GACzC,WAAW,QAAQ,KAAK;GACxB,eAAe,OAAO,KAAK;GAC5B,CAAC;AACF,SAAO,KAAK,WAAW,QAAQ,UAAU;GAAC,GAAG,QAAQ;GAAU;GAAU,GAAG;GAAK,EAAE;GACjF,KAAK,QAAQ,KAAK;GAClB;GACD,CAAC,CAAC,MAAM,WAAW,KAAK,mBAAmB,OAAO,QAAQ,OAAO,OAAO,CAAC;;CAG5E,cAAsB,SAAiB,MAAgB,UAAgD,EAAE,KAAkD;EACzJ,MAAM,YAAY,QAAQ,aAAa;AACvC,SAAO,IAAI,SAAS,gBAAgB,kBAAkB;GACpD,MAAM,QAAQ,MAAM,SAAS,MAAM;IACjC,KAAK,QAAQ,OAAO,QAAQ,KAAK;IACjC,KAAK,QAAQ;IACb,OAAO;KAAC;KAAU;KAAQ;KAAO;IACjC,aAAa;IACd,CAAC;GAEF,IAAI,SAAS;GACb,IAAI,SAAS;AACb,SAAM,QAAQ,YAAY,QAAQ;AAClC,SAAM,QAAQ,YAAY,QAAQ;AAClC,SAAM,QAAQ,GAAG,SAAS,UAAkB;AAC1C,cAAU;KACV;AACF,SAAM,QAAQ,GAAG,SAAS,UAAkB;AAC1C,cAAU;KACV;GAEF,MAAM,QAAQ,iBAAiB;AAC7B,UAAM,KAAK,UAAU;AACrB,kCAAc,IAAI,MAAM,2BAA2B,UAAU,IAAI,CAAC;MACjE,UAAU;AAEb,SAAM,GAAG,UAAU,UAAU;AAC3B,iBAAa,MAAM;AACnB,kCAAc,IAAI,MAAM,4BAA4B,OAAO,MAAM,GAAG,CAAC;KACrE;AAEF,SAAM,GAAG,UAAU,SAAS;AAC1B,iBAAa,MAAM;IACnB,MAAM,SAAS,KAAK,mBAAmB,QAAQ,OAAO;AACtD,QAAI,SAAS,GAAG;AACd,oBAAe;MAAE;MAAQ;MAAQ,CAAC;AAClC;;AAEF,kBAAc,IAAI,MAAM,UAAU,4BAA4B,QAAQ,IAAI,CAAC;KAC3E;IACF;;CAGJ,sCAAoD;AAClD,MAAI,KAAK,iBACP;AAEF,eAAa,oBAAoB;GAC/B,sBAAsB;GACtB,4BAA4B;GAC7B,CAAC;AACF,OAAK,mBAAmB;;CAG1B,kCAAgD;AAC9C,MAAI,KAAK,4BACP;AAEF,OAAK,8BAA8B;AACnC,UAAQ,KAAK,SAAS,SAAS;AAC7B,QAAK,cAAc,KAAK,0BAA0B;IAChD,aAAa;IACb,KAAK,QAAQ;IACb;IACD,CAAC;IACF;;CAGJ,uBAA+B,OAAO,WAOjC;EACH,MAAM,SAAS,MAAM,gBAAgB,OAAO;AAC5C,MAAI,OAAO,UAAU,YACnB,QAAO;GAAE,IAAI;GAAM,6BAA6B;GAAO;AAEzD,MAAI,OAAO,UAAU,mBACnB,QAAO;GAAE,IAAI;GAAM,6BAA6B;GAAM;EAGxD,MAAM,QAAQ,CAAC,eAAe,OAAO,qBAAqB;AAC1D,MAAI,OAAO,WACT,OAAM,KAAK,iBAAiB,OAAO,aAAa;AAElD,QAAM,KAAK,4FAA4F;AACvG,QAAM,KAAK,kBAAkB,OAAO,KAAK,4DAA4D;AACrG,QAAM,KAAK,yCAAyC,OAAO,KAAK,iBAAiB,OAAO,KAAK,qBAAqB;AAClH,SAAO;GACL,IAAI;GACJ,SAAS,MAAM,KAAK,KAAK;GAC1B;;CAGH,oBAA4B,OAAO,MAAc,SAAgC;AAC/E,MAAI,eAAe,KAAK,EAAE;AACxB,WAAQ,IAAI,6GAA6G;AACzH;;EAGF,MAAM,WAAW,MAAM,iBAAiB;AACxC,MAAI,CAAC,UAAU;AACb,WAAQ,IAAI,uEAAuE;AACnF;;EAGF,MAAM,aAAa,UAAU,SAAS,GAAG;AACzC,UAAQ,IAAI,uCAAuC,aAAa;AAChE,UAAQ,IAAI,wCAAwC,WAAW,MAAM;AACrE,UAAQ,IAAI,qDAAqD,KAAK,GAAG;AACzE,UAAQ,IAAI,sGAAsG,KAAK,GAAG;AAC1H,UAAQ,IAAI,2EAA2E,KAAK,+DAA+D;;CAG7J,iCAA+C;AAC7C,UAAQ,IAAI,oBAAoB;AAChC,UAAQ,IAAI,oCAAoC;AAChD,UAAQ,IAAI,0DAA0D;AACtE,UAAQ,IAAI,yCAAyC;AACrD,UAAQ,IAAI,2CAA2C;AACvD,UAAQ,IAAI,gEAAgE"}
|
|
@@ -73,7 +73,7 @@ declare class ServiceGatewayManager {
|
|
|
73
73
|
readonly startDeferredChannels: () => Promise<void>;
|
|
74
74
|
private readonly startChannels;
|
|
75
75
|
private createMarketplace;
|
|
76
|
-
|
|
76
|
+
readonly stop: () => Promise<void>;
|
|
77
77
|
private markUiRuntimeReady;
|
|
78
78
|
private createGatewayController;
|
|
79
79
|
private startSupportServices;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-gateway.manager.d.ts","names":[],"sources":["../../src/managers/service-gateway.manager.ts"],"mappings":";;;;;;;;;;;;;;;KA8DK,QAAA,GAAS,YAAA,CAAa,MAAA;AAAA,KACtB,UAAA,GAAa,YAAA,CAAa,UAAA;AAAA,KAE1B,qBAAA;EACH,WAAA,GAAc,OAAA,CAAQ,QAAA;EACtB,WAAA;AAAA;AAAA,KAGU,kBAAA;EACV,cAAA,GAAiB,MAAA,EAAQ,oBAAA,KAAyB,OAAA;EAClD,4BAAA,GAA+B,aAAA;EAC/B,YAAA,GAAe,OAAA;IAAW,WAAA,EAAa,MAAA;IAAyB,IAAA;EAAA,MAAoB,OAAA;EACpF,WAAA,QAAmB,OAAA;EACnB,gBAAA,GAAmB,IAAA,eAAmB,OAAA;EACtC,8BAAA,GAAiC,IAAA,UAAc,KAAA;IAAiC,OAAA;IAAiB,MAAA;EAAA;AAAA;AAAA,cAGtF,qBAAA;EAAA,iBA6BQ,IAAA;EAAA,iBACA,OAAA;EAAA,SA7BV,MAAA,EAAQ,cAAA;EAAA,SACR,WAAA,EAAa,QAAA;EAAA,SACb,UAAA,EAAY,UAAA;EAAA,SACZ,cAAA,EAAgB,cAAA;EAAA,SAChB,UAAA,EAAY,iBAAA;EAAA,SACZ,cAAA,EAAgB,cAAA;EAAA,SAChB,cAAA,EAAgB,oBAAA;EAAA,SAChB,aAAA,EAAe,mBAAA;EAAA,SACf,OAAA,EAAS,OAAA;EAAA,SACT,YAAA,EADgB,oBAAA;EAAA,SAEhB,cAAA;EAAA,SACA,eAAA,EAAiB,kBAAA;EAAA,SACjB,iBAAA,EAAmB,qBAAA;EAAA,SAEnB,aAAA,EAAe,aAAA;EAAA,SACf,QAAA,EAAU,QAAA;EAAA,SACV,WAAA;EAAA,SACA,SAAA;EAAA,SACA,aAAA,EAAe,oBAAA;EAAA,SACf,WAAA,EAAa,oBAAA;EAAA,SACb,UAAA,EAAY,uBAAA;EAAA,SACZ,WAAA,EAAa,yBAAA;EACtB,eAAA,EAAiB,2BAAA;EACjB,SAAA,EAAW,eAAA;EAAA,SACF,YAAA,EAAY,0BAAA;EAAA,QACb,sBAAA;cAGW,IAAA,EAAM,kBAAA,EACN,OAAA,EAAS,qBAAA;EA4D5B,KAAA,QAAkB,OAAA;EAAA,QAaV,KAAA;EAAA,QAOA,qBAAA;EAAA,QAOA,uBAAA;EAAA,QAIA,cAAA;EAAA,QAiCA,qBAAA;EAAA,QAmBA,cAAA;EAAA,QAmBA,oBAAA;EAAA,SAKC,qBAAA,QAAkC,OAAA;EAAA,iBAI1B,aAAA;EAAA,QAWT,iBAAA;EAAA,
|
|
1
|
+
{"version":3,"file":"service-gateway.manager.d.ts","names":[],"sources":["../../src/managers/service-gateway.manager.ts"],"mappings":";;;;;;;;;;;;;;;KA8DK,QAAA,GAAS,YAAA,CAAa,MAAA;AAAA,KACtB,UAAA,GAAa,YAAA,CAAa,UAAA;AAAA,KAE1B,qBAAA;EACH,WAAA,GAAc,OAAA,CAAQ,QAAA;EACtB,WAAA;AAAA;AAAA,KAGU,kBAAA;EACV,cAAA,GAAiB,MAAA,EAAQ,oBAAA,KAAyB,OAAA;EAClD,4BAAA,GAA+B,aAAA;EAC/B,YAAA,GAAe,OAAA;IAAW,WAAA,EAAa,MAAA;IAAyB,IAAA;EAAA,MAAoB,OAAA;EACpF,WAAA,QAAmB,OAAA;EACnB,gBAAA,GAAmB,IAAA,eAAmB,OAAA;EACtC,8BAAA,GAAiC,IAAA,UAAc,KAAA;IAAiC,OAAA;IAAiB,MAAA;EAAA;AAAA;AAAA,cAGtF,qBAAA;EAAA,iBA6BQ,IAAA;EAAA,iBACA,OAAA;EAAA,SA7BV,MAAA,EAAQ,cAAA;EAAA,SACR,WAAA,EAAa,QAAA;EAAA,SACb,UAAA,EAAY,UAAA;EAAA,SACZ,cAAA,EAAgB,cAAA;EAAA,SAChB,UAAA,EAAY,iBAAA;EAAA,SACZ,cAAA,EAAgB,cAAA;EAAA,SAChB,cAAA,EAAgB,oBAAA;EAAA,SAChB,aAAA,EAAe,mBAAA;EAAA,SACf,OAAA,EAAS,OAAA;EAAA,SACT,YAAA,EADgB,oBAAA;EAAA,SAEhB,cAAA;EAAA,SACA,eAAA,EAAiB,kBAAA;EAAA,SACjB,iBAAA,EAAmB,qBAAA;EAAA,SAEnB,aAAA,EAAe,aAAA;EAAA,SACf,QAAA,EAAU,QAAA;EAAA,SACV,WAAA;EAAA,SACA,SAAA;EAAA,SACA,aAAA,EAAe,oBAAA;EAAA,SACf,WAAA,EAAa,oBAAA;EAAA,SACb,UAAA,EAAY,uBAAA;EAAA,SACZ,WAAA,EAAa,yBAAA;EACtB,eAAA,EAAiB,2BAAA;EACjB,SAAA,EAAW,eAAA;EAAA,SACF,YAAA,EAAY,0BAAA;EAAA,QACb,sBAAA;cAGW,IAAA,EAAM,kBAAA,EACN,OAAA,EAAS,qBAAA;EA4D5B,KAAA,QAAkB,OAAA;EAAA,QAaV,KAAA;EAAA,QAOA,qBAAA;EAAA,QAOA,uBAAA;EAAA,QAIA,cAAA;EAAA,QAiCA,qBAAA;EAAA,QAmBA,cAAA;EAAA,QAmBA,oBAAA;EAAA,SAKC,qBAAA,QAAkC,OAAA;EAAA,iBAI1B,aAAA;EAAA,QAWT,iBAAA;EAAA,SASC,IAAA,QAAiB,OAAA;EAAA,QAOlB,kBAAA;EAAA,QAKA,uBAAA;EAAA,QAqBA,oBAAA;EAAA,QAgBA,sBAAA;EAAA,QAiBA,wBAAA;AAAA"}
|
|
@@ -188,7 +188,7 @@ var ServiceGatewayManager = class {
|
|
|
188
188
|
await runtimeLoopTask;
|
|
189
189
|
} finally {
|
|
190
190
|
if (startupTask) await startupTask.catch(() => void 0);
|
|
191
|
-
await this.
|
|
191
|
+
await this.stop();
|
|
192
192
|
}
|
|
193
193
|
};
|
|
194
194
|
startDeferredRuntime = async () => {
|
|
@@ -212,7 +212,7 @@ var ServiceGatewayManager = class {
|
|
|
212
212
|
installBuiltinSkill: this.deps.installBuiltinMarketplaceSkill
|
|
213
213
|
}).createInstaller()
|
|
214
214
|
});
|
|
215
|
-
|
|
215
|
+
stop = async () => {
|
|
216
216
|
localUiRuntimeStore.clearIfOwnedByProcess();
|
|
217
217
|
await this.fileWatchers.clear();
|
|
218
218
|
await this.kernel.extensions.stop();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-gateway.manager.js","names":["getConfigPath","getDataDir","getWorkspacePath","waitForNextTick"],"sources":["../../src/managers/service-gateway.manager.ts"],"sourcesContent":["import * as NextclawCore from \"@nextclaw/core\";\nimport {\n AgentRunClient,\n NextclawKernel,\n runGatewayInboundLoop,\n type AutomationManager,\n type ConfigManager,\n type LlmProviderManager,\n type SessionManager,\n} from \"@nextclaw/kernel\";\nimport type { EventBus, Ingress } from \"@nextclaw/shared\";\nimport {\n startUiServer,\n type MarketplaceApiConfig,\n type UiRouterOptions,\n type UiRuntimeControlHost,\n type UiRuntimeUpdateHost,\n} from \"@nextclaw/server\";\nimport { resolve } from \"node:path\";\nimport { setImmediate as waitForNextTick } from \"node:timers/promises\";\nimport { GatewayControllerImpl } from \"@nextclaw-service/controllers/gateway.controller.js\";\nimport { GatewayExtensionManager } from \"@nextclaw-service/managers/gateway-extension.manager.js\";\nimport { GatewayRemoteManager } from \"@nextclaw-service/managers/gateway-remote.manager.js\";\nimport { GatewayRestartWakeService } from \"@nextclaw-service/services/gateway/gateway-restart-wake.service.js\";\nimport { createCronJobHandler } from \"@nextclaw-service/utils/gateway-cron-job-handler.utils.js\";\nimport { companionRuntimeService } from \"@nextclaw-service/services/ui/companion-runtime.service.js\";\nimport { handleGatewayDeferredStartupError } from \"@nextclaw-service/utils/gateway-runtime-lifecycle.utils.js\";\nimport { NextclawApp, type UiStartupHandle } from \"@nextclaw-service/services/gateway/nextclaw-app.service.js\";\nimport { NextclawDistributionService } from \"@nextclaw-service/services/runtime/nextclaw-distribution.service.js\";\nimport { ServiceBootstrapStatusStore } from \"@nextclaw-service/services/gateway/service-bootstrap-status.service.js\";\nimport { GatewayRuntimeSupportService, ServiceFileWatcherRegistry, markLocalUiRuntimeIfStarted, watchServiceConfigFile } from \"@nextclaw-service/services/gateway/service-startup-support.service.js\";\nimport { ServiceMarketplaceInstaller } from \"@nextclaw-service/services/marketplace/service-marketplace-installer.service.js\";\nimport { NpmRuntimeUpdateHost } from \"@nextclaw-service/services/runtime/npm-runtime-update-host.service.js\";\nimport { createRuntimeControlHost } from \"@nextclaw-service/services/ui/runtime-control-host.service.js\";\nimport { localUiRuntimeStore } from \"@nextclaw-service/stores/local-ui-runtime.store.js\";\nimport { managedServiceStateStore } from \"@nextclaw-service/stores/managed-service-state.store.js\";\nimport type { RequestRestartParams } from \"@nextclaw-service/types/cli.types.js\";\nimport { openBrowser, resolveUiConfig, resolveUiStaticDir } from \"@nextclaw-service/utils/cli.utils.js\";\nimport { readPanelAppClientSdkScript } from \"@nextclaw-service/utils/panel-app-client-sdk/panel-app-client-sdk-script.utils.js\";\nimport { logStartupTrace, measureStartupAsync, measureStartupSync } from \"@nextclaw-service/utils/startup-trace.utils.js\";\n\nconst {\n getConfigPath,\n getDataDir,\n getWorkspacePath,\n} = NextclawCore;\n\nfunction resolveApplyRestartMode(uiPort: number): \"managed-service-restart\" | \"manual-process-restart\" {\n const serviceState = managedServiceStateStore.read();\n if (serviceState?.pid === process.pid) {\n return \"managed-service-restart\";\n }\n if (\n process.env.NEXTCLAW_RUNTIME_BUNDLE_CHILD === \"1\" &&\n typeof serviceState?.uiPort === \"number\" &&\n serviceState.uiPort === uiPort\n ) {\n return \"managed-service-restart\";\n }\n return \"manual-process-restart\";\n}\n\ntype Config = NextclawCore.Config;\ntype MessageBus = NextclawCore.MessageBus;\n\ntype GatewayRuntimeOptions = {\n uiOverrides?: Partial<Config[\"ui\"]>;\n uiStaticDir?: string | null;\n};\n\nexport type GatewayRuntimeDeps = {\n requestRestart: (params: RequestRestartParams) => Promise<void>;\n initializeAgentHomeDirectory: (homeDirectory: string) => void;\n startService: (options: { uiOverrides: Record<string, unknown>; open: boolean }) => Promise<void>;\n stopService: () => Promise<void>;\n runCliSubcommand: (args: string[]) => Promise<string>;\n installBuiltinMarketplaceSkill: (slug: string, force: boolean | undefined) => { message: string; output?: string } | null;\n};\n\nexport class ServiceGatewayManager {\n readonly kernel: NextclawKernel;\n readonly appEventBus: EventBus;\n readonly messageBus: MessageBus;\n readonly sessionManager: SessionManager;\n readonly automation: AutomationManager;\n readonly agentRunClient: AgentRunClient;\n readonly runtimeControl: UiRuntimeControlHost;\n readonly runtimeUpdate: UiRuntimeUpdateHost | null;\n readonly ingress: Ingress;\n readonly distribution = NextclawDistributionService.get();\n readonly productVersion: string;\n readonly providerManager: LlmProviderManager;\n readonly gatewayController: GatewayControllerImpl;\n\n readonly configManager: ConfigManager;\n readonly uiConfig: Config[\"ui\"];\n readonly uiStaticDir: string | null;\n readonly workspace: string;\n readonly remoteManager: GatewayRemoteManager;\n readonly marketplace: MarketplaceApiConfig;\n readonly extensions: GatewayExtensionManager;\n readonly restartWake: GatewayRestartWakeService;\n bootstrapStatus: ServiceBootstrapStatusStore;\n uiStartup: UiStartupHandle;\n readonly fileWatchers = new ServiceFileWatcherRegistry();\n private deferredChannelStarter: () => Promise<void>;\n\n constructor(\n private readonly deps: GatewayRuntimeDeps,\n private readonly options: GatewayRuntimeOptions,\n ) {\n const configPath = getConfigPath();\n this.kernel = measureStartupSync(\n \"service.gateway.kernel\",\n () => new NextclawKernel({\n homeDir: getDataDir(),\n configPath,\n }),\n );\n this.configManager = this.kernel.configManager;\n const config = this.configManager.config;\n this.uiConfig = resolveUiConfig(config, options.uiOverrides);\n this.uiStaticDir = options.uiStaticDir === undefined ? resolveUiStaticDir(this.distribution.uiDistDir) : options.uiStaticDir;\n this.workspace = getWorkspacePath(config.agents.defaults.workspace);\n this.appEventBus = this.kernel.eventBus;\n this.ingress = this.kernel.ingress;\n this.agentRunClient = new AgentRunClient({\n eventBus: this.kernel.eventBus,\n ingress: this.kernel.ingress,\n });\n this.messageBus = this.kernel.messageBus;\n this.sessionManager = this.kernel.sessionManager;\n this.automation = this.kernel.automation;\n this.productVersion = this.distribution.version;\n this.extensions = new GatewayExtensionManager(this);\n this.providerManager = this.kernel.llmProviders;\n this.installConfigHostHooks();\n this.restartWake = new GatewayRestartWakeService(this);\n this.bootstrapStatus = this.createBootstrapStatus();\n this.uiStartup = this.createDisabledUiStartup();\n this.remoteManager = new GatewayRemoteManager({\n deps: this.deps,\n configManager: this.configManager,\n uiConfig: this.uiConfig,\n onRemoteStateChange: (state) => this.bootstrapStatus.syncRemoteRuntimeState(state),\n });\n this.marketplace = this.createMarketplace();\n this.runtimeControl = createRuntimeControlHost({\n serviceCommands: this.deps,\n requestRestart: this.deps.requestRestart,\n uiConfig: this.uiConfig,\n });\n this.runtimeUpdate =\n process.env.NEXTCLAW_DISABLE_RUNTIME_UPDATE_HOST === \"1\"\n ? null\n : new NpmRuntimeUpdateHost({\n eventBus: this.appEventBus,\n applyRestartMode: resolveApplyRestartMode(this.uiConfig.port),\n requestRestart: this.deps.requestRestart,\n uiConfig: this.uiConfig,\n });\n this.gatewayController = this.createGatewayController();\n this.kernel.provideGatewayController(this.gatewayController);\n this.deferredChannelStarter = this.startChannels;\n this.automation.onJob = createCronJobHandler({\n agentRunClient: this.agentRunClient,\n });\n }\n\n start = async (): Promise<void> => {\n logStartupTrace(\"service.start_gateway.begin\");\n await this.reset();\n this.configureIngressHandlers();\n this.uiStartup = await this.startUiRuntime();\n await companionRuntimeService.applyConfig(this.configManager.config);\n await this.markUiRuntimeReady();\n await this.startSupportServices();\n await this.runRuntimeLoop();\n await companionRuntimeService.ensureStopped();\n logStartupTrace(\"service.start_gateway.end\");\n };\n\n private reset = async (): Promise<void> => {\n await this.fileWatchers.clear();\n this.bootstrapStatus = this.createBootstrapStatus();\n this.uiStartup = this.createDisabledUiStartup();\n this.deferredChannelStarter = this.startChannels;\n };\n\n private createBootstrapStatus = (): ServiceBootstrapStatusStore => {\n const bootstrapStatus = new ServiceBootstrapStatusStore();\n bootstrapStatus.markChannelsPending();\n bootstrapStatus.setRemoteState(this.configManager.config.remote.enabled ? \"pending\" : \"disabled\");\n return bootstrapStatus;\n };\n\n private createDisabledUiStartup = (): UiStartupHandle => ({\n endpoint: \"\",\n });\n\n private startUiRuntime = async (): Promise<UiStartupHandle> => {\n const uiStartup = await measureStartupAsync(\"service.start_ui_runtime\", async () => {\n logStartupTrace(\"service.start_ui_runtime.begin\");\n if (!this.uiConfig.enabled) {\n return this.createDisabledUiStartup();\n }\n const uiServer = await startUiServer(this.createUiRouterOptions());\n const uiUrl = NextclawCore.resolveLocalUiBaseUrl({\n host: uiServer.host,\n port: uiServer.port,\n });\n console.log(`✓ UI API: ${uiUrl}/api`);\n if (this.uiStaticDir) {\n console.log(`✓ UI frontend: ${uiUrl}`);\n }\n if (this.uiConfig.open) {\n openBrowser(uiUrl);\n }\n logStartupTrace(\"service.start_ui_runtime.ready\", {\n host: uiServer.host,\n port: uiServer.port,\n });\n return {\n endpoint: uiUrl,\n };\n });\n markLocalUiRuntimeIfStarted({\n uiStartup: this.uiConfig.enabled ? uiStartup : null,\n uiConfig: this.uiConfig,\n });\n return uiStartup;\n };\n\n private createUiRouterOptions = (): UiRouterOptions => ({\n kernel: this.kernel,\n configPath: this.configManager.configPath,\n appEventBus: this.appEventBus,\n uiConfig: this.uiConfig,\n uiStaticDir: this.uiStaticDir,\n panelAppClientSdkScript: readPanelAppClientSdkScript,\n productVersion: this.productVersion,\n applyLiveConfigReload: this.configManager.applyLiveConfigReload,\n initializeAgentHomeDirectory: this.deps.initializeAgentHomeDirectory,\n marketplace: this.marketplace,\n cron: this.automation,\n remoteAccess: this.remoteManager.remoteAccess,\n runtimeControl: this.runtimeControl,\n ...(this.runtimeUpdate ? { runtimeUpdate: this.runtimeUpdate } : {}),\n bootstrapStatus: this.bootstrapStatus,\n extensions: this.extensions,\n });\n\n private runRuntimeLoop = async (): Promise<void> => {\n logStartupTrace(\"service.start_gateway.runtime_loop_begin\");\n let startupTask: Promise<void> | null = null;\n try {\n const runtimeLoopTask = runGatewayInboundLoop(this);\n startupTask = this.startDeferredRuntime();\n void startupTask.catch((error) => handleGatewayDeferredStartupError({\n bootstrapStatus: this.bootstrapStatus,\n error,\n }));\n await runtimeLoopTask;\n } finally {\n if (startupTask) {\n await startupTask.catch(() => undefined);\n }\n await this.cleanup();\n }\n };\n\n private startDeferredRuntime = async (): Promise<void> => {\n const app = new NextclawApp(this);\n await app.start();\n };\n\n readonly startDeferredChannels = async (): Promise<void> => {\n await this.deferredChannelStarter();\n };\n\n private readonly startChannels = async (): Promise<void> => {\n await this.kernel.channels.start();\n const enabledChannels = this.kernel.channels.enabledChannels;\n if (enabledChannels.length > 0) {\n console.log(`✓ Channels enabled: ${enabledChannels.join(\", \")}`);\n } else {\n console.log(\"Warning: No channels enabled\");\n }\n this.bootstrapStatus.markChannelsReady(enabledChannels);\n };\n\n private createMarketplace = (): MarketplaceApiConfig => ({\n apiBaseUrl: process.env.NEXTCLAW_MARKETPLACE_API_BASE,\n installer: new ServiceMarketplaceInstaller({\n applyLiveConfigReload: this.configManager.applyLiveConfigReload,\n runCliSubcommand: this.deps.runCliSubcommand,\n installBuiltinSkill: this.deps.installBuiltinMarketplaceSkill,\n }).createInstaller(),\n });\n\n private cleanup = async (): Promise<void> => {\n localUiRuntimeStore.clearIfOwnedByProcess();\n await this.fileWatchers.clear();\n await this.kernel.extensions.stop();\n await this.remoteManager.stop();\n };\n\n private markUiRuntimeReady = async (): Promise<void> => {\n this.bootstrapStatus.markShellReady();\n await waitForNextTick();\n };\n\n private createGatewayController = (): GatewayControllerImpl => {\n return measureStartupSync(\n \"service.gateway.gateway_controller\",\n () => new GatewayControllerImpl({\n configManager: this.configManager,\n channels: this.kernel.channels,\n cron: this.automation,\n sessionManager: this.sessionManager,\n requestRestart: async (options) => {\n await this.deps.requestRestart({\n reason: options?.reason ?? \"gateway tool restart\",\n manualMessage: \"Restart the gateway to apply changes.\",\n strategy: \"background-service-or-exit\",\n delayMs: options?.delayMs,\n silentOnServiceRestart: true,\n });\n },\n })\n );\n };\n\n private startSupportServices = async (): Promise<void> => {\n this.extensions.publishConfigChanges();\n await measureStartupAsync(\"service.start_gateway_support_services\", async () =>\n await new GatewayRuntimeSupportService({\n automation: this.automation,\n remoteModule: this.remoteManager.remoteModule,\n watchConfigFile: () => watchServiceConfigFile({\n configPath: resolve(getConfigPath()),\n watcherRegistry: this.fileWatchers,\n scheduleReload: (reason) => this.configManager.scheduleReload(reason)\n }),\n watcherRegistry: this.fileWatchers\n }).start()\n );\n };\n\n private installConfigHostHooks = (): void => {\n this.configManager.installRuntimeHooks({\n reloadCompanion: async ({ config: nextConfig }) => {\n await companionRuntimeService.applyConfig(nextConfig);\n },\n onRestartRequired: (paths) => {\n void this.deps.requestRestart({\n changedPaths: paths,\n manualMessage: `已保存以下改动,等待你手动重启后生效:${paths.join(\", \")}`,\n mode: \"notify\",\n reason: `config reload requires restart: ${paths.join(\", \")}`,\n strategy: \"background-service-or-manual\",\n });\n },\n });\n };\n\n private configureIngressHandlers = (): void => {\n this.kernel.extensions.registerIngressHandlers();\n };\n\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"service-gateway.manager.js","names":["getConfigPath","getDataDir","getWorkspacePath","waitForNextTick"],"sources":["../../src/managers/service-gateway.manager.ts"],"sourcesContent":["import * as NextclawCore from \"@nextclaw/core\";\nimport {\n AgentRunClient,\n NextclawKernel,\n runGatewayInboundLoop,\n type AutomationManager,\n type ConfigManager,\n type LlmProviderManager,\n type SessionManager,\n} from \"@nextclaw/kernel\";\nimport type { EventBus, Ingress } from \"@nextclaw/shared\";\nimport {\n startUiServer,\n type MarketplaceApiConfig,\n type UiRouterOptions,\n type UiRuntimeControlHost,\n type UiRuntimeUpdateHost,\n} from \"@nextclaw/server\";\nimport { resolve } from \"node:path\";\nimport { setImmediate as waitForNextTick } from \"node:timers/promises\";\nimport { GatewayControllerImpl } from \"@nextclaw-service/controllers/gateway.controller.js\";\nimport { GatewayExtensionManager } from \"@nextclaw-service/managers/gateway-extension.manager.js\";\nimport { GatewayRemoteManager } from \"@nextclaw-service/managers/gateway-remote.manager.js\";\nimport { GatewayRestartWakeService } from \"@nextclaw-service/services/gateway/gateway-restart-wake.service.js\";\nimport { createCronJobHandler } from \"@nextclaw-service/utils/gateway-cron-job-handler.utils.js\";\nimport { companionRuntimeService } from \"@nextclaw-service/services/ui/companion-runtime.service.js\";\nimport { handleGatewayDeferredStartupError } from \"@nextclaw-service/utils/gateway-runtime-lifecycle.utils.js\";\nimport { NextclawApp, type UiStartupHandle } from \"@nextclaw-service/services/gateway/nextclaw-app.service.js\";\nimport { NextclawDistributionService } from \"@nextclaw-service/services/runtime/nextclaw-distribution.service.js\";\nimport { ServiceBootstrapStatusStore } from \"@nextclaw-service/services/gateway/service-bootstrap-status.service.js\";\nimport { GatewayRuntimeSupportService, ServiceFileWatcherRegistry, markLocalUiRuntimeIfStarted, watchServiceConfigFile } from \"@nextclaw-service/services/gateway/service-startup-support.service.js\";\nimport { ServiceMarketplaceInstaller } from \"@nextclaw-service/services/marketplace/service-marketplace-installer.service.js\";\nimport { NpmRuntimeUpdateHost } from \"@nextclaw-service/services/runtime/npm-runtime-update-host.service.js\";\nimport { createRuntimeControlHost } from \"@nextclaw-service/services/ui/runtime-control-host.service.js\";\nimport { localUiRuntimeStore } from \"@nextclaw-service/stores/local-ui-runtime.store.js\";\nimport { managedServiceStateStore } from \"@nextclaw-service/stores/managed-service-state.store.js\";\nimport type { RequestRestartParams } from \"@nextclaw-service/types/cli.types.js\";\nimport { openBrowser, resolveUiConfig, resolveUiStaticDir } from \"@nextclaw-service/utils/cli.utils.js\";\nimport { readPanelAppClientSdkScript } from \"@nextclaw-service/utils/panel-app-client-sdk/panel-app-client-sdk-script.utils.js\";\nimport { logStartupTrace, measureStartupAsync, measureStartupSync } from \"@nextclaw-service/utils/startup-trace.utils.js\";\n\nconst {\n getConfigPath,\n getDataDir,\n getWorkspacePath,\n} = NextclawCore;\n\nfunction resolveApplyRestartMode(uiPort: number): \"managed-service-restart\" | \"manual-process-restart\" {\n const serviceState = managedServiceStateStore.read();\n if (serviceState?.pid === process.pid) {\n return \"managed-service-restart\";\n }\n if (\n process.env.NEXTCLAW_RUNTIME_BUNDLE_CHILD === \"1\" &&\n typeof serviceState?.uiPort === \"number\" &&\n serviceState.uiPort === uiPort\n ) {\n return \"managed-service-restart\";\n }\n return \"manual-process-restart\";\n}\n\ntype Config = NextclawCore.Config;\ntype MessageBus = NextclawCore.MessageBus;\n\ntype GatewayRuntimeOptions = {\n uiOverrides?: Partial<Config[\"ui\"]>;\n uiStaticDir?: string | null;\n};\n\nexport type GatewayRuntimeDeps = {\n requestRestart: (params: RequestRestartParams) => Promise<void>;\n initializeAgentHomeDirectory: (homeDirectory: string) => void;\n startService: (options: { uiOverrides: Record<string, unknown>; open: boolean }) => Promise<void>;\n stopService: () => Promise<void>;\n runCliSubcommand: (args: string[]) => Promise<string>;\n installBuiltinMarketplaceSkill: (slug: string, force: boolean | undefined) => { message: string; output?: string } | null;\n};\n\nexport class ServiceGatewayManager {\n readonly kernel: NextclawKernel;\n readonly appEventBus: EventBus;\n readonly messageBus: MessageBus;\n readonly sessionManager: SessionManager;\n readonly automation: AutomationManager;\n readonly agentRunClient: AgentRunClient;\n readonly runtimeControl: UiRuntimeControlHost;\n readonly runtimeUpdate: UiRuntimeUpdateHost | null;\n readonly ingress: Ingress;\n readonly distribution = NextclawDistributionService.get();\n readonly productVersion: string;\n readonly providerManager: LlmProviderManager;\n readonly gatewayController: GatewayControllerImpl;\n\n readonly configManager: ConfigManager;\n readonly uiConfig: Config[\"ui\"];\n readonly uiStaticDir: string | null;\n readonly workspace: string;\n readonly remoteManager: GatewayRemoteManager;\n readonly marketplace: MarketplaceApiConfig;\n readonly extensions: GatewayExtensionManager;\n readonly restartWake: GatewayRestartWakeService;\n bootstrapStatus: ServiceBootstrapStatusStore;\n uiStartup: UiStartupHandle;\n readonly fileWatchers = new ServiceFileWatcherRegistry();\n private deferredChannelStarter: () => Promise<void>;\n\n constructor(\n private readonly deps: GatewayRuntimeDeps,\n private readonly options: GatewayRuntimeOptions,\n ) {\n const configPath = getConfigPath();\n this.kernel = measureStartupSync(\n \"service.gateway.kernel\",\n () => new NextclawKernel({\n homeDir: getDataDir(),\n configPath,\n }),\n );\n this.configManager = this.kernel.configManager;\n const config = this.configManager.config;\n this.uiConfig = resolveUiConfig(config, options.uiOverrides);\n this.uiStaticDir = options.uiStaticDir === undefined ? resolveUiStaticDir(this.distribution.uiDistDir) : options.uiStaticDir;\n this.workspace = getWorkspacePath(config.agents.defaults.workspace);\n this.appEventBus = this.kernel.eventBus;\n this.ingress = this.kernel.ingress;\n this.agentRunClient = new AgentRunClient({\n eventBus: this.kernel.eventBus,\n ingress: this.kernel.ingress,\n });\n this.messageBus = this.kernel.messageBus;\n this.sessionManager = this.kernel.sessionManager;\n this.automation = this.kernel.automation;\n this.productVersion = this.distribution.version;\n this.extensions = new GatewayExtensionManager(this);\n this.providerManager = this.kernel.llmProviders;\n this.installConfigHostHooks();\n this.restartWake = new GatewayRestartWakeService(this);\n this.bootstrapStatus = this.createBootstrapStatus();\n this.uiStartup = this.createDisabledUiStartup();\n this.remoteManager = new GatewayRemoteManager({\n deps: this.deps,\n configManager: this.configManager,\n uiConfig: this.uiConfig,\n onRemoteStateChange: (state) => this.bootstrapStatus.syncRemoteRuntimeState(state),\n });\n this.marketplace = this.createMarketplace();\n this.runtimeControl = createRuntimeControlHost({\n serviceCommands: this.deps,\n requestRestart: this.deps.requestRestart,\n uiConfig: this.uiConfig,\n });\n this.runtimeUpdate =\n process.env.NEXTCLAW_DISABLE_RUNTIME_UPDATE_HOST === \"1\"\n ? null\n : new NpmRuntimeUpdateHost({\n eventBus: this.appEventBus,\n applyRestartMode: resolveApplyRestartMode(this.uiConfig.port),\n requestRestart: this.deps.requestRestart,\n uiConfig: this.uiConfig,\n });\n this.gatewayController = this.createGatewayController();\n this.kernel.provideGatewayController(this.gatewayController);\n this.deferredChannelStarter = this.startChannels;\n this.automation.onJob = createCronJobHandler({\n agentRunClient: this.agentRunClient,\n });\n }\n\n start = async (): Promise<void> => {\n logStartupTrace(\"service.start_gateway.begin\");\n await this.reset();\n this.configureIngressHandlers();\n this.uiStartup = await this.startUiRuntime();\n await companionRuntimeService.applyConfig(this.configManager.config);\n await this.markUiRuntimeReady();\n await this.startSupportServices();\n await this.runRuntimeLoop();\n await companionRuntimeService.ensureStopped();\n logStartupTrace(\"service.start_gateway.end\");\n };\n\n private reset = async (): Promise<void> => {\n await this.fileWatchers.clear();\n this.bootstrapStatus = this.createBootstrapStatus();\n this.uiStartup = this.createDisabledUiStartup();\n this.deferredChannelStarter = this.startChannels;\n };\n\n private createBootstrapStatus = (): ServiceBootstrapStatusStore => {\n const bootstrapStatus = new ServiceBootstrapStatusStore();\n bootstrapStatus.markChannelsPending();\n bootstrapStatus.setRemoteState(this.configManager.config.remote.enabled ? \"pending\" : \"disabled\");\n return bootstrapStatus;\n };\n\n private createDisabledUiStartup = (): UiStartupHandle => ({\n endpoint: \"\",\n });\n\n private startUiRuntime = async (): Promise<UiStartupHandle> => {\n const uiStartup = await measureStartupAsync(\"service.start_ui_runtime\", async () => {\n logStartupTrace(\"service.start_ui_runtime.begin\");\n if (!this.uiConfig.enabled) {\n return this.createDisabledUiStartup();\n }\n const uiServer = await startUiServer(this.createUiRouterOptions());\n const uiUrl = NextclawCore.resolveLocalUiBaseUrl({\n host: uiServer.host,\n port: uiServer.port,\n });\n console.log(`✓ UI API: ${uiUrl}/api`);\n if (this.uiStaticDir) {\n console.log(`✓ UI frontend: ${uiUrl}`);\n }\n if (this.uiConfig.open) {\n openBrowser(uiUrl);\n }\n logStartupTrace(\"service.start_ui_runtime.ready\", {\n host: uiServer.host,\n port: uiServer.port,\n });\n return {\n endpoint: uiUrl,\n };\n });\n markLocalUiRuntimeIfStarted({\n uiStartup: this.uiConfig.enabled ? uiStartup : null,\n uiConfig: this.uiConfig,\n });\n return uiStartup;\n };\n\n private createUiRouterOptions = (): UiRouterOptions => ({\n kernel: this.kernel,\n configPath: this.configManager.configPath,\n appEventBus: this.appEventBus,\n uiConfig: this.uiConfig,\n uiStaticDir: this.uiStaticDir,\n panelAppClientSdkScript: readPanelAppClientSdkScript,\n productVersion: this.productVersion,\n applyLiveConfigReload: this.configManager.applyLiveConfigReload,\n initializeAgentHomeDirectory: this.deps.initializeAgentHomeDirectory,\n marketplace: this.marketplace,\n cron: this.automation,\n remoteAccess: this.remoteManager.remoteAccess,\n runtimeControl: this.runtimeControl,\n ...(this.runtimeUpdate ? { runtimeUpdate: this.runtimeUpdate } : {}),\n bootstrapStatus: this.bootstrapStatus,\n extensions: this.extensions,\n });\n\n private runRuntimeLoop = async (): Promise<void> => {\n logStartupTrace(\"service.start_gateway.runtime_loop_begin\");\n let startupTask: Promise<void> | null = null;\n try {\n const runtimeLoopTask = runGatewayInboundLoop(this);\n startupTask = this.startDeferredRuntime();\n void startupTask.catch((error) => handleGatewayDeferredStartupError({\n bootstrapStatus: this.bootstrapStatus,\n error,\n }));\n await runtimeLoopTask;\n } finally {\n if (startupTask) {\n await startupTask.catch(() => undefined);\n }\n await this.stop();\n }\n };\n\n private startDeferredRuntime = async (): Promise<void> => {\n const app = new NextclawApp(this);\n await app.start();\n };\n\n readonly startDeferredChannels = async (): Promise<void> => {\n await this.deferredChannelStarter();\n };\n\n private readonly startChannels = async (): Promise<void> => {\n await this.kernel.channels.start();\n const enabledChannels = this.kernel.channels.enabledChannels;\n if (enabledChannels.length > 0) {\n console.log(`✓ Channels enabled: ${enabledChannels.join(\", \")}`);\n } else {\n console.log(\"Warning: No channels enabled\");\n }\n this.bootstrapStatus.markChannelsReady(enabledChannels);\n };\n\n private createMarketplace = (): MarketplaceApiConfig => ({\n apiBaseUrl: process.env.NEXTCLAW_MARKETPLACE_API_BASE,\n installer: new ServiceMarketplaceInstaller({\n applyLiveConfigReload: this.configManager.applyLiveConfigReload,\n runCliSubcommand: this.deps.runCliSubcommand,\n installBuiltinSkill: this.deps.installBuiltinMarketplaceSkill,\n }).createInstaller(),\n });\n\n readonly stop = async (): Promise<void> => {\n localUiRuntimeStore.clearIfOwnedByProcess();\n await this.fileWatchers.clear();\n await this.kernel.extensions.stop();\n await this.remoteManager.stop();\n };\n\n private markUiRuntimeReady = async (): Promise<void> => {\n this.bootstrapStatus.markShellReady();\n await waitForNextTick();\n };\n\n private createGatewayController = (): GatewayControllerImpl => {\n return measureStartupSync(\n \"service.gateway.gateway_controller\",\n () => new GatewayControllerImpl({\n configManager: this.configManager,\n channels: this.kernel.channels,\n cron: this.automation,\n sessionManager: this.sessionManager,\n requestRestart: async (options) => {\n await this.deps.requestRestart({\n reason: options?.reason ?? \"gateway tool restart\",\n manualMessage: \"Restart the gateway to apply changes.\",\n strategy: \"background-service-or-exit\",\n delayMs: options?.delayMs,\n silentOnServiceRestart: true,\n });\n },\n })\n );\n };\n\n private startSupportServices = async (): Promise<void> => {\n this.extensions.publishConfigChanges();\n await measureStartupAsync(\"service.start_gateway_support_services\", async () =>\n await new GatewayRuntimeSupportService({\n automation: this.automation,\n remoteModule: this.remoteManager.remoteModule,\n watchConfigFile: () => watchServiceConfigFile({\n configPath: resolve(getConfigPath()),\n watcherRegistry: this.fileWatchers,\n scheduleReload: (reason) => this.configManager.scheduleReload(reason)\n }),\n watcherRegistry: this.fileWatchers\n }).start()\n );\n };\n\n private installConfigHostHooks = (): void => {\n this.configManager.installRuntimeHooks({\n reloadCompanion: async ({ config: nextConfig }) => {\n await companionRuntimeService.applyConfig(nextConfig);\n },\n onRestartRequired: (paths) => {\n void this.deps.requestRestart({\n changedPaths: paths,\n manualMessage: `已保存以下改动,等待你手动重启后生效:${paths.join(\", \")}`,\n mode: \"notify\",\n reason: `config reload requires restart: ${paths.join(\", \")}`,\n strategy: \"background-service-or-manual\",\n });\n },\n });\n };\n\n private configureIngressHandlers = (): void => {\n this.kernel.extensions.registerIngressHandlers();\n };\n\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,MAAM,EACJ,eAAA,iBACA,YAAA,cACA,kBAAA,uBACE;AAEJ,SAAS,wBAAwB,QAAsE;CACrG,MAAM,eAAe,yBAAyB,MAAM;AACpD,KAAI,cAAc,QAAQ,QAAQ,IAChC,QAAO;AAET,KACE,QAAQ,IAAI,kCAAkC,OAC9C,OAAO,cAAc,WAAW,YAChC,aAAa,WAAW,OAExB,QAAO;AAET,QAAO;;AAoBT,IAAa,wBAAb,MAAmC;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,eAAwB,4BAA4B,KAAK;CACzD;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,eAAwB,IAAI,4BAA4B;CACxD;CAEA,YACE,MACA,SACA;AAFiB,OAAA,OAAA;AACA,OAAA,UAAA;EAEjB,MAAM,aAAaA,iBAAe;AAClC,OAAK,SAAS,mBACZ,gCACM,IAAI,eAAe;GACvB,SAASC,cAAY;GACrB;GACD,CAAC,CACH;AACD,OAAK,gBAAgB,KAAK,OAAO;EACjC,MAAM,SAAS,KAAK,cAAc;AAClC,OAAK,WAAW,gBAAgB,QAAQ,QAAQ,YAAY;AAC5D,OAAK,cAAc,QAAQ,gBAAgB,KAAA,IAAY,mBAAmB,KAAK,aAAa,UAAU,GAAG,QAAQ;AACjH,OAAK,YAAYC,mBAAiB,OAAO,OAAO,SAAS,UAAU;AACnE,OAAK,cAAc,KAAK,OAAO;AAC/B,OAAK,UAAU,KAAK,OAAO;AAC3B,OAAK,iBAAiB,IAAI,eAAe;GACvC,UAAU,KAAK,OAAO;GACtB,SAAS,KAAK,OAAO;GACtB,CAAC;AACF,OAAK,aAAa,KAAK,OAAO;AAC9B,OAAK,iBAAiB,KAAK,OAAO;AAClC,OAAK,aAAa,KAAK,OAAO;AAC9B,OAAK,iBAAiB,KAAK,aAAa;AACxC,OAAK,aAAa,IAAI,wBAAwB,KAAK;AACnD,OAAK,kBAAkB,KAAK,OAAO;AACnC,OAAK,wBAAwB;AAC7B,OAAK,cAAc,IAAI,0BAA0B,KAAK;AACtD,OAAK,kBAAkB,KAAK,uBAAuB;AACnD,OAAK,YAAY,KAAK,yBAAyB;AAC/C,OAAK,gBAAgB,IAAI,qBAAqB;GAC5C,MAAM,KAAK;GACX,eAAe,KAAK;GACpB,UAAU,KAAK;GACf,sBAAsB,UAAU,KAAK,gBAAgB,uBAAuB,MAAM;GACnF,CAAC;AACF,OAAK,cAAc,KAAK,mBAAmB;AAC3C,OAAK,iBAAiB,yBAAyB;GAC7C,iBAAiB,KAAK;GACtB,gBAAgB,KAAK,KAAK;GAC1B,UAAU,KAAK;GAChB,CAAC;AACF,OAAK,gBACH,QAAQ,IAAI,yCAAyC,MACjD,OACA,IAAI,qBAAqB;GACvB,UAAU,KAAK;GACf,kBAAkB,wBAAwB,KAAK,SAAS,KAAK;GAC7D,gBAAgB,KAAK,KAAK;GAC1B,UAAU,KAAK;GACtB,CAAC;AACF,OAAK,oBAAoB,KAAK,yBAAyB;AACvD,OAAK,OAAO,yBAAyB,KAAK,kBAAkB;AAC5D,OAAK,yBAAyB,KAAK;AACnC,OAAK,WAAW,QAAQ,qBAAqB,EAC3C,gBAAgB,KAAK,gBACtB,CAAC;;CAGJ,QAAQ,YAA2B;AACjC,kBAAgB,8BAA8B;AAC9C,QAAM,KAAK,OAAO;AAClB,OAAK,0BAA0B;AAC/B,OAAK,YAAY,MAAM,KAAK,gBAAgB;AAC5C,QAAM,wBAAwB,YAAY,KAAK,cAAc,OAAO;AACpE,QAAM,KAAK,oBAAoB;AAC/B,QAAM,KAAK,sBAAsB;AACjC,QAAM,KAAK,gBAAgB;AAC3B,QAAM,wBAAwB,eAAe;AAC7C,kBAAgB,4BAA4B;;CAG9C,QAAgB,YAA2B;AACzC,QAAM,KAAK,aAAa,OAAO;AAC/B,OAAK,kBAAkB,KAAK,uBAAuB;AACnD,OAAK,YAAY,KAAK,yBAAyB;AAC/C,OAAK,yBAAyB,KAAK;;CAGrC,8BAAmE;EACjE,MAAM,kBAAkB,IAAI,6BAA6B;AACzD,kBAAgB,qBAAqB;AACrC,kBAAgB,eAAe,KAAK,cAAc,OAAO,OAAO,UAAU,YAAY,WAAW;AACjG,SAAO;;CAGT,iCAA0D,EACxD,UAAU,IACX;CAED,iBAAyB,YAAsC;EAC7D,MAAM,YAAY,MAAM,oBAAoB,4BAA4B,YAAY;AAClF,mBAAgB,iCAAiC;AACjD,OAAI,CAAC,KAAK,SAAS,QACjB,QAAO,KAAK,yBAAyB;GAEvC,MAAM,WAAW,MAAM,cAAc,KAAK,uBAAuB,CAAC;GAClE,MAAM,QAAQ,aAAa,sBAAsB;IAC/C,MAAM,SAAS;IACf,MAAM,SAAS;IAChB,CAAC;AACF,WAAQ,IAAI,aAAa,MAAM,MAAM;AACrC,OAAI,KAAK,YACP,SAAQ,IAAI,kBAAkB,QAAQ;AAExC,OAAI,KAAK,SAAS,KAChB,aAAY,MAAM;AAEpB,mBAAgB,kCAAkC;IAChD,MAAM,SAAS;IACf,MAAM,SAAS;IAChB,CAAC;AACF,UAAO,EACL,UAAU,OACX;IACD;AACF,8BAA4B;GAC1B,WAAW,KAAK,SAAS,UAAU,YAAY;GAC/C,UAAU,KAAK;GAChB,CAAC;AACF,SAAO;;CAGT,+BAAwD;EACtD,QAAQ,KAAK;EACb,YAAY,KAAK,cAAc;EAC/B,aAAa,KAAK;EAClB,UAAU,KAAK;EACf,aAAa,KAAK;EAClB,yBAAyB;EACzB,gBAAgB,KAAK;EACrB,uBAAuB,KAAK,cAAc;EAC1C,8BAA8B,KAAK,KAAK;EACxC,aAAa,KAAK;EAClB,MAAM,KAAK;EACX,cAAc,KAAK,cAAc;EACjC,gBAAgB,KAAK;EACrB,GAAI,KAAK,gBAAgB,EAAE,eAAe,KAAK,eAAe,GAAG,EAAE;EACnE,iBAAiB,KAAK;EACtB,YAAY,KAAK;EAClB;CAED,iBAAyB,YAA2B;AAClD,kBAAgB,2CAA2C;EAC3D,IAAI,cAAoC;AACxC,MAAI;GACF,MAAM,kBAAkB,sBAAsB,KAAK;AACnD,iBAAc,KAAK,sBAAsB;AACpC,eAAY,OAAO,UAAU,kCAAkC;IAClE,iBAAiB,KAAK;IACtB;IACD,CAAC,CAAC;AACH,SAAM;YACE;AACR,OAAI,YACF,OAAM,YAAY,YAAY,KAAA,EAAU;AAE1C,SAAM,KAAK,MAAM;;;CAIrB,uBAA+B,YAA2B;AAExD,QADY,IAAI,YAAY,KAAK,CACvB,OAAO;;CAGnB,wBAAiC,YAA2B;AAC1D,QAAM,KAAK,wBAAwB;;CAGrC,gBAAiC,YAA2B;AAC1D,QAAM,KAAK,OAAO,SAAS,OAAO;EAClC,MAAM,kBAAkB,KAAK,OAAO,SAAS;AAC7C,MAAI,gBAAgB,SAAS,EAC3B,SAAQ,IAAI,uBAAuB,gBAAgB,KAAK,KAAK,GAAG;MAEhE,SAAQ,IAAI,+BAA+B;AAE7C,OAAK,gBAAgB,kBAAkB,gBAAgB;;CAGzD,2BAAyD;EACvD,YAAY,QAAQ,IAAI;EACxB,WAAW,IAAI,4BAA4B;GACzC,uBAAuB,KAAK,cAAc;GAC1C,kBAAkB,KAAK,KAAK;GAC5B,qBAAqB,KAAK,KAAK;GAChC,CAAC,CAAC,iBAAiB;EACrB;CAED,OAAgB,YAA2B;AACzC,sBAAoB,uBAAuB;AAC3C,QAAM,KAAK,aAAa,OAAO;AAC/B,QAAM,KAAK,OAAO,WAAW,MAAM;AACnC,QAAM,KAAK,cAAc,MAAM;;CAGjC,qBAA6B,YAA2B;AACtD,OAAK,gBAAgB,gBAAgB;AACrC,QAAMC,cAAiB;;CAGzB,gCAA+D;AAC7D,SAAO,mBACL,4CACM,IAAI,sBAAsB;GAC9B,eAAe,KAAK;GACpB,UAAU,KAAK,OAAO;GACtB,MAAM,KAAK;GACX,gBAAgB,KAAK;GACrB,gBAAgB,OAAO,YAAY;AACjC,UAAM,KAAK,KAAK,eAAe;KAC7B,QAAQ,SAAS,UAAU;KAC3B,eAAe;KACf,UAAU;KACV,SAAS,SAAS;KAClB,wBAAwB;KACzB,CAAC;;GAEL,CAAC,CACH;;CAGH,uBAA+B,YAA2B;AACxD,OAAK,WAAW,sBAAsB;AACtC,QAAM,oBAAoB,0CAA0C,YAClE,MAAM,IAAI,6BAA6B;GACrC,YAAY,KAAK;GACjB,cAAc,KAAK,cAAc;GACjC,uBAAuB,uBAAuB;IAC5C,YAAY,QAAQH,iBAAe,CAAC;IACpC,iBAAiB,KAAK;IACtB,iBAAiB,WAAW,KAAK,cAAc,eAAe,OAAO;IACtE,CAAC;GACF,iBAAiB,KAAK;GACvB,CAAC,CAAC,OAAO,CACX;;CAGH,+BAA6C;AAC3C,OAAK,cAAc,oBAAoB;GACrC,iBAAiB,OAAO,EAAE,QAAQ,iBAAiB;AACjD,UAAM,wBAAwB,YAAY,WAAW;;GAEvD,oBAAoB,UAAU;AACvB,SAAK,KAAK,eAAe;KAC5B,cAAc;KACd,eAAe,sBAAsB,MAAM,KAAK,KAAK;KACrD,MAAM;KACN,QAAQ,mCAAmC,MAAM,KAAK,KAAK;KAC3D,UAAU;KACX,CAAC;;GAEL,CAAC;;CAGJ,iCAA+C;AAC7C,OAAK,OAAO,WAAW,yBAAyB"}
|
|
@@ -4,6 +4,8 @@ import * as NextclawCore from "@nextclaw/core";
|
|
|
4
4
|
import { ChildProcess } from "node:child_process";
|
|
5
5
|
|
|
6
6
|
//#region src/services/runtime/managed-service-supervisor.service.d.ts
|
|
7
|
+
declare const MANAGED_SERVICE_EXIT_SIGNALS: readonly ["SIGHUP", "SIGINT", "SIGTERM"];
|
|
8
|
+
type ManagedServiceExitSignal = typeof MANAGED_SERVICE_EXIT_SIGNALS[number];
|
|
7
9
|
type Config$1 = NextclawCore.Config;
|
|
8
10
|
type ManagedServiceStartup = {
|
|
9
11
|
child: ChildProcess;
|
|
@@ -26,19 +28,30 @@ type ManagedServiceSupervisorOptions = {
|
|
|
26
28
|
stateStore?: ManagedServiceStateStore;
|
|
27
29
|
now?: () => Date;
|
|
28
30
|
isProcessRunningFn?: (pid: number) => boolean;
|
|
31
|
+
exitProcess?: (code: number) => never | void;
|
|
32
|
+
processEvents?: Pick<NodeJS.Process, "once">;
|
|
29
33
|
heartbeatIntervalMs?: number;
|
|
30
34
|
leaseTtlMs?: number;
|
|
31
35
|
};
|
|
36
|
+
type CurrentProcessLifecycleOptions = {
|
|
37
|
+
onSignal?: (params: {
|
|
38
|
+
signal: ManagedServiceExitSignal;
|
|
39
|
+
code: number;
|
|
40
|
+
}) => Promise<void> | void;
|
|
41
|
+
};
|
|
32
42
|
declare class ManagedServiceSupervisor {
|
|
33
43
|
private readonly stateStore;
|
|
34
44
|
private readonly now;
|
|
35
45
|
private readonly isProcessRunningFn;
|
|
46
|
+
private readonly exitProcess;
|
|
47
|
+
private readonly processEvents;
|
|
36
48
|
private readonly heartbeatIntervalMs;
|
|
37
49
|
private readonly leaseTtlMs;
|
|
38
50
|
private readonly serviceStartupLogger;
|
|
39
51
|
private heartbeatTimer;
|
|
40
52
|
private lifecycleTrackingInstalled;
|
|
41
53
|
private pendingExit;
|
|
54
|
+
private signalExitInProgress;
|
|
42
55
|
constructor(options?: ManagedServiceSupervisorOptions);
|
|
43
56
|
spawnManagedService: (params: {
|
|
44
57
|
appName: string;
|
|
@@ -70,7 +83,8 @@ declare class ManagedServiceSupervisor {
|
|
|
70
83
|
};
|
|
71
84
|
snapshot: ManagedServiceSnapshot;
|
|
72
85
|
}) => ManagedServiceState;
|
|
73
|
-
installCurrentProcessLifecycleTracking: () => void;
|
|
86
|
+
installCurrentProcessLifecycleTracking: (options?: CurrentProcessLifecycleOptions) => void;
|
|
87
|
+
private readonly handleCurrentProcessSignal;
|
|
74
88
|
startHeartbeatForCurrentProcess: (pid?: number) => void;
|
|
75
89
|
stopHeartbeatForCurrentProcess: () => void;
|
|
76
90
|
resolveStateLiveness: (state: ManagedServiceState | null) => ManagedServiceLiveness;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"managed-service-supervisor.service.d.ts","names":[],"sources":["../../../src/services/runtime/managed-service-supervisor.service.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"managed-service-supervisor.service.d.ts","names":[],"sources":["../../../src/services/runtime/managed-service-supervisor.service.ts"],"mappings":";;;;;;cAoBM,4BAAA;AAAA,KACD,wBAAA,UAAkC,4BAAA;AAAA,KAQlC,QAAA,GAAS,YAAA,CAAa,MAAA;AAAA,KAEtB,qBAAA;EACH,KAAA,EAAO,YAAA;EACP,OAAA;EACA,kBAAA;EACA,mBAAA;EACA,sBAAA;EACA,QAAA,EAAU,sBAAA;AAAA;AAAA,KAGP,sBAAA;EACH,aAAA;EACA,OAAA;EACA,UAAA;EACA,WAAA;EACA,YAAA;EACA,YAAA;EACA,eAAA;AAAA;AAAA,KAGG,+BAAA;EACH,UAAA,GAAa,wBAAA;EACb,GAAA,SAAY,IAAA;EACZ,kBAAA,IAAsB,GAAA;EACtB,WAAA,IAAe,IAAA;EACf,aAAA,GAAgB,IAAA,CAAK,MAAA,CAAO,OAAA;EAC5B,mBAAA;EACA,UAAA;AAAA;AAAA,KAGG,8BAAA;EACH,QAAA,IAAY,MAAA;IAAU,MAAA,EAAQ,wBAAA;IAA0B,IAAA;EAAA,MAAmB,OAAA;AAAA;AAAA,cAGhE,wBAAA;EAAA,iBACM,UAAA;EAAA,iBACA,GAAA;EAAA,iBACA,kBAAA;EAAA,iBACA,WAAA;EAAA,iBACA,aAAA;EAAA,iBACA,mBAAA;EAAA,iBACA,UAAA;EAAA,iBACA,oBAAA;EAAA,QACT,cAAA;EAAA,QACA,0BAAA;EAAA,QACA,WAAA;EAAA,QACA,oBAAA;cAEI,OAAA,GAAS,+BAAA;EAUrB,mBAAA,GAAuB,MAAA;IACrB,OAAA;IACA,MAAA,EAAQ,QAAA;IACR,QAAA;MAAY,IAAA;MAAc,IAAA;IAAA;IAC1B,KAAA;IACA,MAAA;IACA,SAAA;IACA,gBAAA;IACA,uBAAA,GAA0B,iBAAA;IAC1B,kBAAA,GAAqB,OAAA,UAAiB,OAAA;IACtC,8BAAA,GAAiC,MAAA;MAC/B,KAAA;MACA,MAAA;MACA,SAAA;MACA,OAAA;MACA,cAAA;IAAA;IAEF,qBAAA;EAAA,MACE,qBAAA;EA6FJ,eAAA,GAAmB,MAAA;IACjB,kBAAA;IACA,SAAA;MAAa,KAAA;MAAgB,cAAA;IAAA;IAC7B,QAAA,EAAU,sBAAA;EAAA,MACR,mBAAA;EAOJ,sCAAA,GAA0C,OAAA,GAAS,8BAAA;EAAA,iBAmClC,0BAAA;EA8BjB,+BAAA,GAAmC,GAAA;EAenC,8BAAA;EAIA,oBAAA,GAAwB,KAAA,EAAO,mBAAA,YAA6B,sBAAA;EAAA,QAgDpD,cAAA;EAuBR,wBAAA,GAA4B,IAAA,EAAM,sBAAA;EAAA,QAY1B,aAAA;EAAA,iBAQS,WAAA;EAAA,QAOT,kBAAA;AAAA"}
|
|
@@ -11,6 +11,11 @@ import { dirname } from "node:path";
|
|
|
11
11
|
//#region src/services/runtime/managed-service-supervisor.service.ts
|
|
12
12
|
const DEFAULT_HEARTBEAT_INTERVAL_MS = 2e3;
|
|
13
13
|
const DEFAULT_LEASE_TTL_MS = 1e4;
|
|
14
|
+
const MANAGED_SERVICE_EXIT_SIGNALS = [
|
|
15
|
+
"SIGHUP",
|
|
16
|
+
"SIGINT",
|
|
17
|
+
"SIGTERM"
|
|
18
|
+
];
|
|
14
19
|
const SIGNAL_EXIT_CODES = {
|
|
15
20
|
SIGHUP: 129,
|
|
16
21
|
SIGINT: 130,
|
|
@@ -20,16 +25,21 @@ var ManagedServiceSupervisor = class {
|
|
|
20
25
|
stateStore;
|
|
21
26
|
now;
|
|
22
27
|
isProcessRunningFn;
|
|
28
|
+
exitProcess;
|
|
29
|
+
processEvents;
|
|
23
30
|
heartbeatIntervalMs;
|
|
24
31
|
leaseTtlMs;
|
|
25
32
|
serviceStartupLogger = NextclawCore.getAppLogger("service.startup");
|
|
26
33
|
heartbeatTimer = null;
|
|
27
34
|
lifecycleTrackingInstalled = false;
|
|
28
35
|
pendingExit = null;
|
|
36
|
+
signalExitInProgress = false;
|
|
29
37
|
constructor(options = {}) {
|
|
30
38
|
this.stateStore = options.stateStore ?? managedServiceStateStore;
|
|
31
39
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
32
40
|
this.isProcessRunningFn = options.isProcessRunningFn ?? isProcessRunning;
|
|
41
|
+
this.exitProcess = options.exitProcess ?? ((code) => process.exit(code));
|
|
42
|
+
this.processEvents = options.processEvents ?? process;
|
|
33
43
|
this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
34
44
|
this.leaseTtlMs = options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;
|
|
35
45
|
}
|
|
@@ -119,27 +129,14 @@ var ManagedServiceSupervisor = class {
|
|
|
119
129
|
lease: this.createLease(params.snapshot.pid)
|
|
120
130
|
});
|
|
121
131
|
};
|
|
122
|
-
installCurrentProcessLifecycleTracking = () => {
|
|
132
|
+
installCurrentProcessLifecycleTracking = (options = {}) => {
|
|
123
133
|
if (this.lifecycleTrackingInstalled) return;
|
|
124
134
|
this.lifecycleTrackingInstalled = true;
|
|
125
135
|
this.startHeartbeatForCurrentProcess();
|
|
126
|
-
for (const signal of
|
|
127
|
-
|
|
128
|
-
"SIGINT",
|
|
129
|
-
"SIGTERM"
|
|
130
|
-
]) process.once(signal, () => {
|
|
131
|
-
this.pendingExit = {
|
|
132
|
-
pid: process.pid,
|
|
133
|
-
reason: "signal",
|
|
134
|
-
exitedAt: this.now().toISOString(),
|
|
135
|
-
code: SIGNAL_EXIT_CODES[signal],
|
|
136
|
-
signal
|
|
137
|
-
};
|
|
138
|
-
this.recordCurrentProcessExit(this.pendingExit);
|
|
139
|
-
this.stopHeartbeat();
|
|
140
|
-
process.exit(SIGNAL_EXIT_CODES[signal]);
|
|
136
|
+
for (const signal of MANAGED_SERVICE_EXIT_SIGNALS) this.processEvents.once(signal, () => {
|
|
137
|
+
this.handleCurrentProcessSignal(signal, options.onSignal);
|
|
141
138
|
});
|
|
142
|
-
|
|
139
|
+
this.processEvents.once("uncaughtExceptionMonitor", (error) => {
|
|
143
140
|
this.pendingExit = {
|
|
144
141
|
pid: process.pid,
|
|
145
142
|
reason: "uncaughtException",
|
|
@@ -147,7 +144,7 @@ var ManagedServiceSupervisor = class {
|
|
|
147
144
|
message: error instanceof Error ? error.message : String(error)
|
|
148
145
|
};
|
|
149
146
|
});
|
|
150
|
-
|
|
147
|
+
this.processEvents.once("exit", (code) => {
|
|
151
148
|
this.recordCurrentProcessExit({
|
|
152
149
|
...this.pendingExit ?? {
|
|
153
150
|
pid: process.pid,
|
|
@@ -159,6 +156,33 @@ var ManagedServiceSupervisor = class {
|
|
|
159
156
|
this.stopHeartbeat();
|
|
160
157
|
});
|
|
161
158
|
};
|
|
159
|
+
handleCurrentProcessSignal = async (signal, onSignal) => {
|
|
160
|
+
if (this.signalExitInProgress) return;
|
|
161
|
+
this.signalExitInProgress = true;
|
|
162
|
+
const code = SIGNAL_EXIT_CODES[signal];
|
|
163
|
+
this.pendingExit = {
|
|
164
|
+
pid: process.pid,
|
|
165
|
+
reason: "signal",
|
|
166
|
+
exitedAt: this.now().toISOString(),
|
|
167
|
+
code,
|
|
168
|
+
signal
|
|
169
|
+
};
|
|
170
|
+
this.recordCurrentProcessExit(this.pendingExit);
|
|
171
|
+
this.stopHeartbeat();
|
|
172
|
+
try {
|
|
173
|
+
await onSignal?.({
|
|
174
|
+
signal,
|
|
175
|
+
code
|
|
176
|
+
});
|
|
177
|
+
} catch (error) {
|
|
178
|
+
this.serviceStartupLogger.warn("runtime.process.shutdown_failed", {
|
|
179
|
+
signal,
|
|
180
|
+
error: error instanceof Error ? error.message : String(error)
|
|
181
|
+
});
|
|
182
|
+
} finally {
|
|
183
|
+
this.exitProcess(code);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
162
186
|
startHeartbeatForCurrentProcess = (pid = process.pid) => {
|
|
163
187
|
if (this.heartbeatTimer) return;
|
|
164
188
|
if (!this.writeHeartbeat(pid)) return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"managed-service-supervisor.service.js","names":[],"sources":["../../../src/services/runtime/managed-service-supervisor.service.ts"],"sourcesContent":["import { spawn, type ChildProcess } from \"node:child_process\";\nimport { mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport * as NextclawCore from \"@nextclaw/core\";\nimport { FileLogSink } from \"@nextclaw/core\";\nimport {\n managedServiceStateStore,\n type ManagedServiceLastExit,\n type ManagedServiceLease,\n type ManagedServiceState,\n type ManagedServiceStateStore\n} from \"@nextclaw-service/stores/managed-service-state.store.js\";\nimport { writeInitialManagedServiceState, writeReadyManagedServiceState } from \"@nextclaw-service/utils/runtime/service-remote-runtime.utils.js\";\nimport type { ManagedServiceSnapshot } from \"@nextclaw-service/utils/runtime/managed-service-routing.utils.js\";\nimport { resolveCliSubcommandLaunch } from \"@nextclaw-service/utils/marketplace/cli-subcommand-launch.utils.js\";\nimport { createTopLevelNextclawCommandEnv } from \"@nextclaw-service/utils/top-level-nextclaw-command-env.utils.js\";\nimport { isProcessRunning, resolveServiceLogPath } from \"@nextclaw-service/utils/cli.utils.js\";\n\nconst DEFAULT_HEARTBEAT_INTERVAL_MS = 2_000;\nconst DEFAULT_LEASE_TTL_MS = 10_000;\nconst SIGNAL_EXIT_CODES: Record<string, number> = {\n SIGHUP: 129,\n SIGINT: 130,\n SIGTERM: 143\n};\n\ntype Config = NextclawCore.Config;\n\ntype ManagedServiceStartup = {\n child: ChildProcess;\n logPath: string;\n readinessTimeoutMs: number;\n quickPhaseTimeoutMs: number;\n extendedPhaseTimeoutMs: number;\n snapshot: ManagedServiceSnapshot;\n};\n\ntype ManagedServiceLiveness = {\n processExists: boolean;\n running: boolean;\n staleState: boolean;\n staleReason: \"process-not-running\" | \"lease-expired\" | null;\n leaseExpired: boolean;\n leaseMissing: boolean;\n lastHeartbeatAt: string | null;\n};\n\ntype ManagedServiceSupervisorOptions = {\n stateStore?: ManagedServiceStateStore;\n now?: () => Date;\n isProcessRunningFn?: (pid: number) => boolean;\n heartbeatIntervalMs?: number;\n leaseTtlMs?: number;\n};\n\nexport class ManagedServiceSupervisor {\n private readonly stateStore: ManagedServiceStateStore;\n private readonly now: () => Date;\n private readonly isProcessRunningFn: (pid: number) => boolean;\n private readonly heartbeatIntervalMs: number;\n private readonly leaseTtlMs: number;\n private readonly serviceStartupLogger = NextclawCore.getAppLogger(\"service.startup\");\n private heartbeatTimer: NodeJS.Timeout | null = null;\n private lifecycleTrackingInstalled = false;\n private pendingExit: ManagedServiceLastExit | null = null;\n\n constructor(options: ManagedServiceSupervisorOptions = {}) {\n this.stateStore = options.stateStore ?? managedServiceStateStore;\n this.now = options.now ?? (() => new Date());\n this.isProcessRunningFn = options.isProcessRunningFn ?? isProcessRunning;\n this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;\n this.leaseTtlMs = options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;\n }\n\n spawnManagedService = (params: {\n appName: string;\n config: Config;\n uiConfig: { host: string; port: number };\n uiUrl: string;\n apiUrl: string;\n healthUrl: string;\n startupTimeoutMs?: number;\n resolveStartupTimeoutMs: (overrideTimeoutMs: number | undefined) => number;\n appendStartupStage: (logPath: string, message: string) => void;\n printStartupFailureDiagnostics: (params: {\n uiUrl: string;\n apiUrl: string;\n healthUrl: string;\n logPath: string;\n lastProbeError: string | null;\n }) => void;\n resolveServiceLogPath?: () => string;\n }): ManagedServiceStartup | null => {\n const {\n appName,\n apiUrl,\n appendStartupStage,\n config,\n healthUrl,\n printStartupFailureDiagnostics,\n resolveStartupTimeoutMs,\n startupTimeoutMs,\n uiConfig,\n uiUrl\n } = params;\n const logPath = (params.resolveServiceLogPath ?? resolveServiceLogPath)();\n new FileLogSink({ serviceLogPath: logPath }).ensureReady();\n mkdirSync(dirname(logPath), { recursive: true });\n const readinessTimeoutMs = resolveStartupTimeoutMs(startupTimeoutMs);\n const quickPhaseTimeoutMs = Math.min(8000, readinessTimeoutMs);\n const extendedPhaseTimeoutMs = Math.max(0, readinessTimeoutMs - quickPhaseTimeoutMs);\n appendStartupStage(\n logPath,\n `start requested: ui=${uiConfig.host}:${uiConfig.port}, readinessTimeoutMs=${readinessTimeoutMs}`\n );\n console.log(`Starting ${appName} background service (readiness timeout ${Math.ceil(readinessTimeoutMs / 1000)}s)...`);\n\n const cliLaunch = resolveCliSubcommandLaunch({\n argvEntry: process.argv[1],\n importMetaUrl: import.meta.url,\n cliArgs: [\"serve\", \"--ui-port\", String(uiConfig.port)],\n nodePath: process.execPath\n });\n const childArgs = [...process.execArgv, ...cliLaunch.args];\n appendStartupStage(logPath, `spawning background process: ${cliLaunch.command} ${childArgs.join(\" \")}`);\n const child = spawn(cliLaunch.command, childArgs, {\n env: createTopLevelNextclawCommandEnv(process.env),\n stdio: \"ignore\",\n detached: true,\n windowsHide: true\n });\n appendStartupStage(logPath, `spawned background process pid=${child.pid ?? \"unknown\"}`);\n if (!child.pid) {\n appendStartupStage(logPath, \"spawn failed: child pid missing\");\n console.error(\"Error: Failed to start background service.\");\n printStartupFailureDiagnostics({\n uiUrl,\n apiUrl,\n healthUrl,\n logPath,\n lastProbeError: null\n });\n return null;\n }\n\n const snapshot: ManagedServiceSnapshot = {\n pid: child.pid,\n uiUrl,\n apiUrl,\n uiHost: uiConfig.host,\n uiPort: uiConfig.port,\n logPath\n };\n writeInitialManagedServiceState({\n config,\n lease: this.createLease(child.pid),\n readinessTimeoutMs,\n snapshot\n });\n this.serviceStartupLogger.info(\"runtime.process.started\", {\n runtimeKind: \"managed-service\",\n childPid: child.pid,\n uiUrl,\n apiUrl,\n uiHost: uiConfig.host,\n uiPort: uiConfig.port,\n entrypoint: `${cliLaunch.command} ${childArgs.join(\" \")}`\n });\n this.serviceStartupLogger.info(\"service_state.written\", {\n runtimeKind: \"managed-service\",\n childPid: child.pid,\n statePath: this.stateStore.path,\n uiUrl,\n apiUrl\n });\n return {\n child,\n logPath,\n readinessTimeoutMs,\n quickPhaseTimeoutMs,\n extendedPhaseTimeoutMs,\n snapshot\n };\n };\n\n writeReadyState = (params: {\n readinessTimeoutMs: number;\n readiness: { ready: boolean; lastProbeError: string | null };\n snapshot: ManagedServiceSnapshot;\n }): ManagedServiceState => {\n return writeReadyManagedServiceState({\n ...params,\n lease: this.createLease(params.snapshot.pid)\n });\n };\n\n installCurrentProcessLifecycleTracking = (): void => {\n if (this.lifecycleTrackingInstalled) {\n return;\n }\n this.lifecycleTrackingInstalled = true;\n this.startHeartbeatForCurrentProcess();\n\n for (const signal of [\"SIGHUP\", \"SIGINT\", \"SIGTERM\"] as const) {\n process.once(signal, () => {\n this.pendingExit = {\n pid: process.pid,\n reason: \"signal\",\n exitedAt: this.now().toISOString(),\n code: SIGNAL_EXIT_CODES[signal],\n signal\n };\n this.recordCurrentProcessExit(this.pendingExit);\n this.stopHeartbeat();\n process.exit(SIGNAL_EXIT_CODES[signal]);\n });\n }\n\n process.once(\"uncaughtExceptionMonitor\", (error) => {\n this.pendingExit = {\n pid: process.pid,\n reason: \"uncaughtException\",\n exitedAt: this.now().toISOString(),\n message: error instanceof Error ? error.message : String(error)\n };\n });\n\n process.once(\"exit\", (code) => {\n this.recordCurrentProcessExit({\n ...(this.pendingExit ?? {\n pid: process.pid,\n reason: \"exit\",\n exitedAt: this.now().toISOString()\n }),\n code\n });\n this.stopHeartbeat();\n });\n };\n\n startHeartbeatForCurrentProcess = (pid = process.pid): void => {\n if (this.heartbeatTimer) {\n return;\n }\n if (!this.writeHeartbeat(pid)) {\n return;\n }\n this.heartbeatTimer = setInterval(() => {\n if (!this.writeHeartbeat(pid)) {\n this.stopHeartbeat();\n }\n }, this.heartbeatIntervalMs);\n this.heartbeatTimer.unref();\n };\n\n stopHeartbeatForCurrentProcess = (): void => {\n this.stopHeartbeat();\n };\n\n resolveStateLiveness = (state: ManagedServiceState | null): ManagedServiceLiveness => {\n if (!state) {\n return {\n processExists: false,\n running: false,\n staleState: false,\n staleReason: null,\n leaseExpired: false,\n leaseMissing: false,\n lastHeartbeatAt: null\n };\n }\n\n const processExists = this.isProcessRunningFn(state.pid);\n const leaseStatus = this.resolveLeaseStatus(state.lease);\n if (!processExists) {\n return {\n processExists,\n running: false,\n staleState: true,\n staleReason: \"process-not-running\",\n leaseExpired: leaseStatus.expired,\n leaseMissing: leaseStatus.missing,\n lastHeartbeatAt: leaseStatus.heartbeatAt\n };\n }\n if (leaseStatus.expired) {\n return {\n processExists,\n running: false,\n staleState: true,\n staleReason: \"lease-expired\",\n leaseExpired: true,\n leaseMissing: false,\n lastHeartbeatAt: leaseStatus.heartbeatAt\n };\n }\n return {\n processExists,\n running: true,\n staleState: false,\n staleReason: null,\n leaseExpired: false,\n leaseMissing: leaseStatus.missing,\n lastHeartbeatAt: leaseStatus.heartbeatAt\n };\n };\n\n private writeHeartbeat = (pid: number): boolean => {\n let wrote = false;\n this.stateStore.update((state) => {\n if (state.pid !== pid) {\n return state;\n }\n wrote = true;\n const next: ManagedServiceState = {\n ...state,\n lease: {\n ...(state.lease ?? this.createLease(pid)),\n ownerPid: pid,\n heartbeatAt: this.now().toISOString(),\n heartbeatIntervalMs: this.heartbeatIntervalMs,\n ttlMs: this.leaseTtlMs\n }\n };\n delete next.lastExit;\n return next;\n });\n return wrote;\n };\n\n recordCurrentProcessExit = (exit: ManagedServiceLastExit): void => {\n this.stateStore.update((state) => {\n if (state.pid !== exit.pid) {\n return state;\n }\n return {\n ...state,\n lastExit: exit\n };\n });\n };\n\n private stopHeartbeat = (): void => {\n if (!this.heartbeatTimer) {\n return;\n }\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n };\n\n private readonly createLease = (ownerPid: number): ManagedServiceLease => ({\n ownerPid,\n heartbeatAt: this.now().toISOString(),\n heartbeatIntervalMs: this.heartbeatIntervalMs,\n ttlMs: this.leaseTtlMs\n });\n\n private resolveLeaseStatus = (lease: ManagedServiceLease | undefined): {\n expired: boolean;\n missing: boolean;\n heartbeatAt: string | null;\n } => {\n if (!lease) {\n return {\n expired: false,\n missing: true,\n heartbeatAt: null\n };\n }\n const heartbeatAtMs = Date.parse(lease.heartbeatAt);\n const ttlMs = Number.isFinite(lease.ttlMs) ? lease.ttlMs : this.leaseTtlMs;\n return {\n expired: !Number.isFinite(heartbeatAtMs) || heartbeatAtMs + ttlMs < this.now().getTime(),\n missing: false,\n heartbeatAt: lease.heartbeatAt\n };\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"managed-service-supervisor.service.js","names":[],"sources":["../../../src/services/runtime/managed-service-supervisor.service.ts"],"sourcesContent":["import { spawn, type ChildProcess } from \"node:child_process\";\nimport { mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport * as NextclawCore from \"@nextclaw/core\";\nimport { FileLogSink } from \"@nextclaw/core\";\nimport {\n managedServiceStateStore,\n type ManagedServiceLastExit,\n type ManagedServiceLease,\n type ManagedServiceState,\n type ManagedServiceStateStore\n} from \"@nextclaw-service/stores/managed-service-state.store.js\";\nimport { writeInitialManagedServiceState, writeReadyManagedServiceState } from \"@nextclaw-service/utils/runtime/service-remote-runtime.utils.js\";\nimport type { ManagedServiceSnapshot } from \"@nextclaw-service/utils/runtime/managed-service-routing.utils.js\";\nimport { resolveCliSubcommandLaunch } from \"@nextclaw-service/utils/marketplace/cli-subcommand-launch.utils.js\";\nimport { createTopLevelNextclawCommandEnv } from \"@nextclaw-service/utils/top-level-nextclaw-command-env.utils.js\";\nimport { isProcessRunning, resolveServiceLogPath } from \"@nextclaw-service/utils/cli.utils.js\";\n\nconst DEFAULT_HEARTBEAT_INTERVAL_MS = 2_000;\nconst DEFAULT_LEASE_TTL_MS = 10_000;\nconst MANAGED_SERVICE_EXIT_SIGNALS = [\"SIGHUP\", \"SIGINT\", \"SIGTERM\"] as const;\ntype ManagedServiceExitSignal = typeof MANAGED_SERVICE_EXIT_SIGNALS[number];\n\nconst SIGNAL_EXIT_CODES: Record<ManagedServiceExitSignal, number> = {\n SIGHUP: 129,\n SIGINT: 130,\n SIGTERM: 143\n};\n\ntype Config = NextclawCore.Config;\n\ntype ManagedServiceStartup = {\n child: ChildProcess;\n logPath: string;\n readinessTimeoutMs: number;\n quickPhaseTimeoutMs: number;\n extendedPhaseTimeoutMs: number;\n snapshot: ManagedServiceSnapshot;\n};\n\ntype ManagedServiceLiveness = {\n processExists: boolean;\n running: boolean;\n staleState: boolean;\n staleReason: \"process-not-running\" | \"lease-expired\" | null;\n leaseExpired: boolean;\n leaseMissing: boolean;\n lastHeartbeatAt: string | null;\n};\n\ntype ManagedServiceSupervisorOptions = {\n stateStore?: ManagedServiceStateStore;\n now?: () => Date;\n isProcessRunningFn?: (pid: number) => boolean;\n exitProcess?: (code: number) => never | void;\n processEvents?: Pick<NodeJS.Process, \"once\">;\n heartbeatIntervalMs?: number;\n leaseTtlMs?: number;\n};\n\ntype CurrentProcessLifecycleOptions = {\n onSignal?: (params: { signal: ManagedServiceExitSignal; code: number }) => Promise<void> | void;\n};\n\nexport class ManagedServiceSupervisor {\n private readonly stateStore: ManagedServiceStateStore;\n private readonly now: () => Date;\n private readonly isProcessRunningFn: (pid: number) => boolean;\n private readonly exitProcess: (code: number) => never | void;\n private readonly processEvents: Pick<NodeJS.Process, \"once\">;\n private readonly heartbeatIntervalMs: number;\n private readonly leaseTtlMs: number;\n private readonly serviceStartupLogger = NextclawCore.getAppLogger(\"service.startup\");\n private heartbeatTimer: NodeJS.Timeout | null = null;\n private lifecycleTrackingInstalled = false;\n private pendingExit: ManagedServiceLastExit | null = null;\n private signalExitInProgress = false;\n\n constructor(options: ManagedServiceSupervisorOptions = {}) {\n this.stateStore = options.stateStore ?? managedServiceStateStore;\n this.now = options.now ?? (() => new Date());\n this.isProcessRunningFn = options.isProcessRunningFn ?? isProcessRunning;\n this.exitProcess = options.exitProcess ?? ((code) => process.exit(code));\n this.processEvents = options.processEvents ?? process;\n this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;\n this.leaseTtlMs = options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;\n }\n\n spawnManagedService = (params: {\n appName: string;\n config: Config;\n uiConfig: { host: string; port: number };\n uiUrl: string;\n apiUrl: string;\n healthUrl: string;\n startupTimeoutMs?: number;\n resolveStartupTimeoutMs: (overrideTimeoutMs: number | undefined) => number;\n appendStartupStage: (logPath: string, message: string) => void;\n printStartupFailureDiagnostics: (params: {\n uiUrl: string;\n apiUrl: string;\n healthUrl: string;\n logPath: string;\n lastProbeError: string | null;\n }) => void;\n resolveServiceLogPath?: () => string;\n }): ManagedServiceStartup | null => {\n const {\n appName,\n apiUrl,\n appendStartupStage,\n config,\n healthUrl,\n printStartupFailureDiagnostics,\n resolveStartupTimeoutMs,\n startupTimeoutMs,\n uiConfig,\n uiUrl\n } = params;\n const logPath = (params.resolveServiceLogPath ?? resolveServiceLogPath)();\n new FileLogSink({ serviceLogPath: logPath }).ensureReady();\n mkdirSync(dirname(logPath), { recursive: true });\n const readinessTimeoutMs = resolveStartupTimeoutMs(startupTimeoutMs);\n const quickPhaseTimeoutMs = Math.min(8000, readinessTimeoutMs);\n const extendedPhaseTimeoutMs = Math.max(0, readinessTimeoutMs - quickPhaseTimeoutMs);\n appendStartupStage(\n logPath,\n `start requested: ui=${uiConfig.host}:${uiConfig.port}, readinessTimeoutMs=${readinessTimeoutMs}`\n );\n console.log(`Starting ${appName} background service (readiness timeout ${Math.ceil(readinessTimeoutMs / 1000)}s)...`);\n\n const cliLaunch = resolveCliSubcommandLaunch({\n argvEntry: process.argv[1],\n importMetaUrl: import.meta.url,\n cliArgs: [\"serve\", \"--ui-port\", String(uiConfig.port)],\n nodePath: process.execPath\n });\n const childArgs = [...process.execArgv, ...cliLaunch.args];\n appendStartupStage(logPath, `spawning background process: ${cliLaunch.command} ${childArgs.join(\" \")}`);\n const child = spawn(cliLaunch.command, childArgs, {\n env: createTopLevelNextclawCommandEnv(process.env),\n stdio: \"ignore\",\n detached: true,\n windowsHide: true\n });\n appendStartupStage(logPath, `spawned background process pid=${child.pid ?? \"unknown\"}`);\n if (!child.pid) {\n appendStartupStage(logPath, \"spawn failed: child pid missing\");\n console.error(\"Error: Failed to start background service.\");\n printStartupFailureDiagnostics({\n uiUrl,\n apiUrl,\n healthUrl,\n logPath,\n lastProbeError: null\n });\n return null;\n }\n\n const snapshot: ManagedServiceSnapshot = {\n pid: child.pid,\n uiUrl,\n apiUrl,\n uiHost: uiConfig.host,\n uiPort: uiConfig.port,\n logPath\n };\n writeInitialManagedServiceState({\n config,\n lease: this.createLease(child.pid),\n readinessTimeoutMs,\n snapshot\n });\n this.serviceStartupLogger.info(\"runtime.process.started\", {\n runtimeKind: \"managed-service\",\n childPid: child.pid,\n uiUrl,\n apiUrl,\n uiHost: uiConfig.host,\n uiPort: uiConfig.port,\n entrypoint: `${cliLaunch.command} ${childArgs.join(\" \")}`\n });\n this.serviceStartupLogger.info(\"service_state.written\", {\n runtimeKind: \"managed-service\",\n childPid: child.pid,\n statePath: this.stateStore.path,\n uiUrl,\n apiUrl\n });\n return {\n child,\n logPath,\n readinessTimeoutMs,\n quickPhaseTimeoutMs,\n extendedPhaseTimeoutMs,\n snapshot\n };\n };\n\n writeReadyState = (params: {\n readinessTimeoutMs: number;\n readiness: { ready: boolean; lastProbeError: string | null };\n snapshot: ManagedServiceSnapshot;\n }): ManagedServiceState => {\n return writeReadyManagedServiceState({\n ...params,\n lease: this.createLease(params.snapshot.pid)\n });\n };\n\n installCurrentProcessLifecycleTracking = (options: CurrentProcessLifecycleOptions = {}): void => {\n if (this.lifecycleTrackingInstalled) {\n return;\n }\n this.lifecycleTrackingInstalled = true;\n this.startHeartbeatForCurrentProcess();\n\n for (const signal of MANAGED_SERVICE_EXIT_SIGNALS) {\n this.processEvents.once(signal, () => {\n void this.handleCurrentProcessSignal(signal, options.onSignal);\n });\n }\n\n this.processEvents.once(\"uncaughtExceptionMonitor\", (error) => {\n this.pendingExit = {\n pid: process.pid,\n reason: \"uncaughtException\",\n exitedAt: this.now().toISOString(),\n message: error instanceof Error ? error.message : String(error)\n };\n });\n\n this.processEvents.once(\"exit\", (code) => {\n this.recordCurrentProcessExit({\n ...(this.pendingExit ?? {\n pid: process.pid,\n reason: \"exit\",\n exitedAt: this.now().toISOString()\n }),\n code\n });\n this.stopHeartbeat();\n });\n };\n\n private readonly handleCurrentProcessSignal = async (\n signal: ManagedServiceExitSignal,\n onSignal: CurrentProcessLifecycleOptions[\"onSignal\"],\n ): Promise<void> => {\n if (this.signalExitInProgress) {\n return;\n }\n this.signalExitInProgress = true;\n const code = SIGNAL_EXIT_CODES[signal];\n this.pendingExit = {\n pid: process.pid,\n reason: \"signal\",\n exitedAt: this.now().toISOString(),\n code,\n signal\n };\n this.recordCurrentProcessExit(this.pendingExit);\n this.stopHeartbeat();\n try {\n await onSignal?.({ signal, code });\n } catch (error) {\n this.serviceStartupLogger.warn(\"runtime.process.shutdown_failed\", {\n signal,\n error: error instanceof Error ? error.message : String(error)\n });\n } finally {\n this.exitProcess(code);\n }\n };\n\n startHeartbeatForCurrentProcess = (pid = process.pid): void => {\n if (this.heartbeatTimer) {\n return;\n }\n if (!this.writeHeartbeat(pid)) {\n return;\n }\n this.heartbeatTimer = setInterval(() => {\n if (!this.writeHeartbeat(pid)) {\n this.stopHeartbeat();\n }\n }, this.heartbeatIntervalMs);\n this.heartbeatTimer.unref();\n };\n\n stopHeartbeatForCurrentProcess = (): void => {\n this.stopHeartbeat();\n };\n\n resolveStateLiveness = (state: ManagedServiceState | null): ManagedServiceLiveness => {\n if (!state) {\n return {\n processExists: false,\n running: false,\n staleState: false,\n staleReason: null,\n leaseExpired: false,\n leaseMissing: false,\n lastHeartbeatAt: null\n };\n }\n\n const processExists = this.isProcessRunningFn(state.pid);\n const leaseStatus = this.resolveLeaseStatus(state.lease);\n if (!processExists) {\n return {\n processExists,\n running: false,\n staleState: true,\n staleReason: \"process-not-running\",\n leaseExpired: leaseStatus.expired,\n leaseMissing: leaseStatus.missing,\n lastHeartbeatAt: leaseStatus.heartbeatAt\n };\n }\n if (leaseStatus.expired) {\n return {\n processExists,\n running: false,\n staleState: true,\n staleReason: \"lease-expired\",\n leaseExpired: true,\n leaseMissing: false,\n lastHeartbeatAt: leaseStatus.heartbeatAt\n };\n }\n return {\n processExists,\n running: true,\n staleState: false,\n staleReason: null,\n leaseExpired: false,\n leaseMissing: leaseStatus.missing,\n lastHeartbeatAt: leaseStatus.heartbeatAt\n };\n };\n\n private writeHeartbeat = (pid: number): boolean => {\n let wrote = false;\n this.stateStore.update((state) => {\n if (state.pid !== pid) {\n return state;\n }\n wrote = true;\n const next: ManagedServiceState = {\n ...state,\n lease: {\n ...(state.lease ?? this.createLease(pid)),\n ownerPid: pid,\n heartbeatAt: this.now().toISOString(),\n heartbeatIntervalMs: this.heartbeatIntervalMs,\n ttlMs: this.leaseTtlMs\n }\n };\n delete next.lastExit;\n return next;\n });\n return wrote;\n };\n\n recordCurrentProcessExit = (exit: ManagedServiceLastExit): void => {\n this.stateStore.update((state) => {\n if (state.pid !== exit.pid) {\n return state;\n }\n return {\n ...state,\n lastExit: exit\n };\n });\n };\n\n private stopHeartbeat = (): void => {\n if (!this.heartbeatTimer) {\n return;\n }\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n };\n\n private readonly createLease = (ownerPid: number): ManagedServiceLease => ({\n ownerPid,\n heartbeatAt: this.now().toISOString(),\n heartbeatIntervalMs: this.heartbeatIntervalMs,\n ttlMs: this.leaseTtlMs\n });\n\n private resolveLeaseStatus = (lease: ManagedServiceLease | undefined): {\n expired: boolean;\n missing: boolean;\n heartbeatAt: string | null;\n } => {\n if (!lease) {\n return {\n expired: false,\n missing: true,\n heartbeatAt: null\n };\n }\n const heartbeatAtMs = Date.parse(lease.heartbeatAt);\n const ttlMs = Number.isFinite(lease.ttlMs) ? lease.ttlMs : this.leaseTtlMs;\n return {\n expired: !Number.isFinite(heartbeatAtMs) || heartbeatAtMs + ttlMs < this.now().getTime(),\n missing: false,\n heartbeatAt: lease.heartbeatAt\n };\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/service",
|
|
3
|
-
"version": "0.2.15-beta.
|
|
3
|
+
"version": "0.2.15-beta.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "NextClaw long-running service host and runtime lifecycle.",
|
|
6
6
|
"type": "module",
|
|
@@ -42,31 +42,31 @@
|
|
|
42
42
|
"chokidar": "^3.6.0",
|
|
43
43
|
"commander": "^12.1.0",
|
|
44
44
|
"jszip": "^3.10.1",
|
|
45
|
-
"@nextclaw/channel-extension-dingtalk": "0.1.19-beta.
|
|
46
|
-
"@nextclaw/channel-extension-discord": "0.1.19-beta.
|
|
47
|
-
"@nextclaw/channel-extension-email": "0.1.19-beta.
|
|
48
|
-
"@nextclaw/channel-extension-feishu": "0.1.26-beta.
|
|
49
|
-
"@nextclaw/channel-extension-qq": "0.1.23-beta.
|
|
50
|
-
"@nextclaw/channel-extension-slack": "0.1.19-beta.
|
|
51
|
-
"@nextclaw/channel-extension-telegram": "0.1.19-beta.
|
|
52
|
-
"@nextclaw/channel-extension-wecom": "0.1.19-beta.
|
|
53
|
-
"@nextclaw/channel-extension-weixin": "0.1.30-beta.
|
|
54
|
-
"@nextclaw/channel-extension-whatsapp": "0.1.19-beta.
|
|
55
|
-
"@nextclaw/client-sdk": "0.4.4-beta.
|
|
56
|
-
"@nextclaw/core": "0.14.5-beta.
|
|
57
|
-
"@nextclaw/
|
|
58
|
-
"@nextclaw/
|
|
59
|
-
"@nextclaw/ncp
|
|
60
|
-
"@nextclaw/ncp-
|
|
61
|
-
"@nextclaw/
|
|
62
|
-
"@nextclaw/
|
|
63
|
-
"@nextclaw/nextclaw-
|
|
64
|
-
"@nextclaw/
|
|
65
|
-
"@nextclaw/runtime": "0.
|
|
66
|
-
"@nextclaw/
|
|
67
|
-
"@nextclaw/
|
|
68
|
-
"@nextclaw/
|
|
69
|
-
"@nextclaw/
|
|
45
|
+
"@nextclaw/channel-extension-dingtalk": "0.1.19-beta.1",
|
|
46
|
+
"@nextclaw/channel-extension-discord": "0.1.19-beta.1",
|
|
47
|
+
"@nextclaw/channel-extension-email": "0.1.19-beta.1",
|
|
48
|
+
"@nextclaw/channel-extension-feishu": "0.1.26-beta.1",
|
|
49
|
+
"@nextclaw/channel-extension-qq": "0.1.23-beta.1",
|
|
50
|
+
"@nextclaw/channel-extension-slack": "0.1.19-beta.1",
|
|
51
|
+
"@nextclaw/channel-extension-telegram": "0.1.19-beta.1",
|
|
52
|
+
"@nextclaw/channel-extension-wecom": "0.1.19-beta.1",
|
|
53
|
+
"@nextclaw/channel-extension-weixin": "0.1.30-beta.1",
|
|
54
|
+
"@nextclaw/channel-extension-whatsapp": "0.1.19-beta.1",
|
|
55
|
+
"@nextclaw/client-sdk": "0.4.4-beta.1",
|
|
56
|
+
"@nextclaw/core": "0.14.5-beta.1",
|
|
57
|
+
"@nextclaw/kernel": "0.5.1-beta.1",
|
|
58
|
+
"@nextclaw/mcp": "0.2.15-beta.1",
|
|
59
|
+
"@nextclaw/ncp": "0.6.4-beta.1",
|
|
60
|
+
"@nextclaw/ncp-agent-runtime": "0.3.45-beta.1",
|
|
61
|
+
"@nextclaw/ncp-mcp": "0.1.110-beta.1",
|
|
62
|
+
"@nextclaw/ncp-toolkit": "0.5.39-beta.1",
|
|
63
|
+
"@nextclaw/nextclaw-hermes-acp-bridge": "0.2.14-beta.1",
|
|
64
|
+
"@nextclaw/nextclaw-ncp-runtime-http-client": "0.2.14-beta.1",
|
|
65
|
+
"@nextclaw/nextclaw-ncp-runtime-stdio-client": "0.2.14-beta.1",
|
|
66
|
+
"@nextclaw/remote": "0.2.15-beta.1",
|
|
67
|
+
"@nextclaw/runtime": "0.3.15-beta.1",
|
|
68
|
+
"@nextclaw/server": "0.14.5-beta.1",
|
|
69
|
+
"@nextclaw/shared": "0.3.1-beta.1"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^20.17.6",
|