mirror of
https://github.com/slhaf/Partner.git
synced 2026-05-12 16:53:04 +08:00
feat(partnerctl): add run and shutdown commands with process lifecycle handling
This commit is contained in:
37
.idea/misc.xml
generated
37
.idea/misc.xml
generated
@@ -1,26 +1,27 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="EntryPointsManager">
|
<component name="EntryPointsManager">
|
||||||
<list size="19">
|
<list size="20">
|
||||||
<item index="0" class="java.lang.String" itemvalue="lombok.Data" />
|
<item index="0" class="java.lang.String" itemvalue="lombok.Data" />
|
||||||
<item index="1" class="java.lang.String" itemvalue="net.bytebuddy.implementation.bind.annotation.RuntimeType" />
|
<item index="1" class="java.lang.String" itemvalue="net.bytebuddy.implementation.bind.annotation.RuntimeType" />
|
||||||
<item index="2" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.Capability" />
|
<item index="2" class="java.lang.String" itemvalue="picocli.CommandLine.Command" />
|
||||||
<item index="3" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityCore" />
|
<item index="3" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.Capability" />
|
||||||
<item index="4" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityMethod" />
|
<item index="4" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityCore" />
|
||||||
<item index="5" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CoordinateManager" />
|
<item index="5" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityMethod" />
|
||||||
<item index="6" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.Coordinated" />
|
<item index="6" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CoordinateManager" />
|
||||||
<item index="7" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.component.annotation.Init" />
|
<item index="7" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.Coordinated" />
|
||||||
<item index="8" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AfterExecute" />
|
<item index="8" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.component.annotation.Init" />
|
||||||
<item index="9" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AgentRunningModule" />
|
<item index="9" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AfterExecute" />
|
||||||
<item index="10" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AgentSubModule" />
|
<item index="10" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AgentRunningModule" />
|
||||||
<item index="11" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.BeforeExecute" />
|
<item index="11" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AgentSubModule" />
|
||||||
<item index="12" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.Init" />
|
<item index="12" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.BeforeExecute" />
|
||||||
<item index="13" class="java.lang.String" itemvalue="work.slhaf.partner.api.capability.annotation.CapabilityMethod" />
|
<item index="13" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.Init" />
|
||||||
<item index="14" class="java.lang.String" itemvalue="work.slhaf.partner.api.capability.annotation.CoordinateManager" />
|
<item index="14" class="java.lang.String" itemvalue="work.slhaf.partner.api.capability.annotation.CapabilityMethod" />
|
||||||
<item index="15" class="java.lang.String" itemvalue="work.slhaf.partner.api.register.capability.annotation.Capability" />
|
<item index="15" class="java.lang.String" itemvalue="work.slhaf.partner.api.capability.annotation.CoordinateManager" />
|
||||||
<item index="16" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityCore" />
|
<item index="16" class="java.lang.String" itemvalue="work.slhaf.partner.api.register.capability.annotation.Capability" />
|
||||||
<item index="17" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityMethod" />
|
<item index="17" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityCore" />
|
||||||
<item index="18" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.component.annotation.AgentComponent" />
|
<item index="18" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityMethod" />
|
||||||
|
<item index="19" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.component.annotation.AgentComponent" />
|
||||||
</list>
|
</list>
|
||||||
<writeAnnotations>
|
<writeAnnotations>
|
||||||
<writeAnnotation name="work.slhaf.partner.api.agent.factory.capability.annotation.InjectCapability" />
|
<writeAnnotation name="work.slhaf.partner.api.agent.factory.capability.annotation.InjectCapability" />
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import kotlin.system.exitProcess
|
|||||||
subcommands = [
|
subcommands = [
|
||||||
InitCommand::class,
|
InitCommand::class,
|
||||||
RunCommand::class,
|
RunCommand::class,
|
||||||
|
ShutdownCommand::class,
|
||||||
ChatCommand::class,
|
ChatCommand::class,
|
||||||
ConfigCommand::class,
|
ConfigCommand::class,
|
||||||
ModuleCommand::class,
|
ModuleCommand::class,
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package work.slhaf.partner.ctl.commands
|
||||||
|
|
||||||
|
import picocli.CommandLine
|
||||||
|
import work.slhaf.partner.ctl.i18n.I18n.text
|
||||||
|
import work.slhaf.partner.ctl.support.CommandInterrupted
|
||||||
|
import work.slhaf.partner.ctl.support.inheritCommand
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
|
@CommandLine.Command(name = "run", description = ["Start Partner agent."])
|
||||||
|
class RunCommand : Runnable {
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
val home = resolvePartnerHome()
|
||||||
|
val partnerJar = resolvePartnerJar(home)
|
||||||
|
|
||||||
|
if (!Files.isRegularFile(partnerJar)) {
|
||||||
|
throw CommandInterrupted(text("control.run.error.jarNotFound", partnerJar))
|
||||||
|
}
|
||||||
|
|
||||||
|
val exitCode = inheritCommand(
|
||||||
|
command = listOf("java", "-jar", partnerJar.toString()),
|
||||||
|
environment = mapOf("PARTNER_HOME" to home.toString()),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (exitCode != 0) {
|
||||||
|
throw CommandInterrupted(text("control.run.error.exited", exitCode), exitCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CommandLine.Command(name = "shutdown", description = ["Shutdown Partner agent."])
|
||||||
|
class ShutdownCommand : Runnable {
|
||||||
|
|
||||||
|
@CommandLine.Option(
|
||||||
|
names = ["--timeout"],
|
||||||
|
description = ["Seconds to wait after graceful termination before failing or forcing shutdown."]
|
||||||
|
)
|
||||||
|
var timeoutSeconds: Long = 10
|
||||||
|
|
||||||
|
@CommandLine.Option(
|
||||||
|
names = ["-f", "--force"],
|
||||||
|
description = ["Forcefully kill matching Partner process if it does not exit before timeout."]
|
||||||
|
)
|
||||||
|
var force: Boolean = false
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
val home = resolvePartnerHome()
|
||||||
|
val partnerJar = resolvePartnerJar(home)
|
||||||
|
val processes = findPartnerProcesses(partnerJar)
|
||||||
|
|
||||||
|
if (processes.isEmpty()) {
|
||||||
|
println(text("control.shutdown.info.notRunning", partnerJar))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var failed = false
|
||||||
|
processes.forEach { process ->
|
||||||
|
val pid = process.pid()
|
||||||
|
println(text("control.shutdown.info.stopping", pid))
|
||||||
|
process.destroy()
|
||||||
|
|
||||||
|
val stopped = waitForExit(process, timeoutSeconds)
|
||||||
|
if (stopped) {
|
||||||
|
println(text("control.shutdown.success.stopped", pid))
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
if (force) {
|
||||||
|
println(text("control.shutdown.warn.force", pid))
|
||||||
|
process.destroyForcibly()
|
||||||
|
if (waitForExit(process, timeoutSeconds)) {
|
||||||
|
println(text("control.shutdown.success.stopped", pid))
|
||||||
|
} else {
|
||||||
|
failed = true
|
||||||
|
println(text("control.shutdown.error.notStopped", pid))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
failed = true
|
||||||
|
println(text("control.shutdown.error.notStoppedUseForce", pid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed) {
|
||||||
|
throw CommandInterrupted(text("control.shutdown.error.failed"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolvePartnerHome(): Path {
|
||||||
|
val home = System.getenv("PARTNER_HOME")
|
||||||
|
?.trim()
|
||||||
|
?.takeIf { it.isNotEmpty() }
|
||||||
|
?.let { Paths.get(it) }
|
||||||
|
?: Paths.get(System.getProperty("user.home"), ".partner")
|
||||||
|
|
||||||
|
return home.toAbsolutePath().normalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolvePartnerJar(home: Path): Path {
|
||||||
|
return home.resolve("resource").resolve("partner-core.jar").toAbsolutePath().normalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findPartnerProcesses(partnerJar: Path): List<ProcessHandle> {
|
||||||
|
val currentPid = ProcessHandle.current().pid()
|
||||||
|
val jarPath = partnerJar.toString()
|
||||||
|
|
||||||
|
return ProcessHandle.allProcesses()
|
||||||
|
.filter { it.pid() != currentPid }
|
||||||
|
.filter { it.isAlive }
|
||||||
|
.filter { process ->
|
||||||
|
val info = process.info()
|
||||||
|
val arguments = info.arguments().orElse(emptyArray()) ?: emptyArray()
|
||||||
|
val commandLine = info.commandLine().orElse("") ?: ""
|
||||||
|
|
||||||
|
arguments.any { it == jarPath } || commandLine.contains(jarPath)
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun waitForExit(process: ProcessHandle, timeoutSeconds: Long): Boolean {
|
||||||
|
return try {
|
||||||
|
process.onExit().get(timeoutSeconds, TimeUnit.SECONDS)
|
||||||
|
true
|
||||||
|
} catch (_: TimeoutException) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package work.slhaf.partner.ctl.commands
|
|
||||||
|
|
||||||
import picocli.CommandLine
|
|
||||||
|
|
||||||
@CommandLine.Command(name = "run")
|
|
||||||
class RunCommand : Runnable{
|
|
||||||
override fun run() {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -89,3 +89,13 @@ sourceBuild.tool.java.reason=Required to run Maven. Command failed: java --versi
|
|||||||
sourceBuild.tool.javac.reason=Required to compile Partner from source. Install a JDK, not just a JRE. Command failed: javac --version
|
sourceBuild.tool.javac.reason=Required to compile Partner from source. Install a JDK, not just a JRE. Command failed: javac --version
|
||||||
sourceBuild.tool.git.reason=Required to clone Partner source. Command failed: git --version
|
sourceBuild.tool.git.reason=Required to clone Partner source. Command failed: git --version
|
||||||
sourceBuild.tool.mvn.reason=Required to build Partner from source. Command failed: mvn --version
|
sourceBuild.tool.mvn.reason=Required to build Partner from source. Command failed: mvn --version
|
||||||
|
|
||||||
|
control.run.error.jarNotFound=Partner runtime jar does not exist: {0}. Run partnerctl init first to initialize Partner.
|
||||||
|
control.run.error.exited=Partner exited with code {0}
|
||||||
|
control.shutdown.info.notRunning=No running Partner process found for {0}
|
||||||
|
control.shutdown.info.stopping=Stopping Partner process pid={0}
|
||||||
|
control.shutdown.success.stopped=Partner process pid={0} stopped
|
||||||
|
control.shutdown.warn.force=Force killing Partner process pid={0}
|
||||||
|
control.shutdown.error.notStopped=Partner process pid={0} did not stop
|
||||||
|
control.shutdown.error.notStoppedUseForce=Partner process pid={0} did not stop. Use --force to kill it forcibly.
|
||||||
|
control.shutdown.error.failed=Failed to stop one or more Partner processes
|
||||||
|
|||||||
@@ -89,3 +89,13 @@ sourceBuild.tool.java.reason=运行 Maven 需要 java。命令失败:java --ve
|
|||||||
sourceBuild.tool.javac.reason=从源码编译 Partner 需要 javac。请安装 JDK,而不只是 JRE。命令失败:javac --version
|
sourceBuild.tool.javac.reason=从源码编译 Partner 需要 javac。请安装 JDK,而不只是 JRE。命令失败:javac --version
|
||||||
sourceBuild.tool.git.reason=克隆 Partner 源码需要 git。命令失败:git --version
|
sourceBuild.tool.git.reason=克隆 Partner 源码需要 git。命令失败:git --version
|
||||||
sourceBuild.tool.mvn.reason=从源码构建 Partner 需要 mvn。命令失败:mvn --version
|
sourceBuild.tool.mvn.reason=从源码构建 Partner 需要 mvn。命令失败:mvn --version
|
||||||
|
|
||||||
|
control.run.error.jarNotFound=Partner runtime jar 不存在:{0}。请先执行 partnerctl init 初始化 Partner。
|
||||||
|
control.run.error.exited=Partner 退出,退出码:{0}
|
||||||
|
control.shutdown.info.notRunning=没有找到对应 {0} 的运行中 Partner 进程
|
||||||
|
control.shutdown.info.stopping=正在停止 Partner 进程 pid={0}
|
||||||
|
control.shutdown.success.stopped=Partner 进程 pid={0} 已停止
|
||||||
|
control.shutdown.warn.force=正在强制结束 Partner 进程 pid={0}
|
||||||
|
control.shutdown.error.notStopped=Partner 进程 pid={0} 未停止
|
||||||
|
control.shutdown.error.notStoppedUseForce=Partner 进程 pid={0} 未停止。使用 --force 可强制结束。
|
||||||
|
control.shutdown.error.failed=一个或多个 Partner 进程停止失败
|
||||||
|
|||||||
Reference in New Issue
Block a user