14 Commits

Author SHA1 Message Date
cb8ddfe4e2 docs: update README startup guide for PartnerCtl 2026-05-14 22:05:37 +08:00
756c0a12ad fix(partnerctl): include zh-CN locale in native image build 2026-05-14 20:41:53 +08:00
8a5b844a4a feat(partnerctl-init): add release download install option 2026-05-14 19:47:18 +08:00
github-actions[bot]
8d29ea4c9e chore(registry): update latest core release to release-core/0.9.0-preview 2026-05-14 09:08:43 +00:00
github-actions[bot]
4770eaf42f chore(registry): update latest buildable to buildable/0.9.0-preview 2026-05-14 09:07:33 +00:00
8bb266a1c3 chore(versioning): bump runtime, framework, and core to 0.9.0-preview 2026-05-14 17:06:06 +08:00
9054a9b4ad fix(onebot): correct interaction api dependency version 2026-05-13 13:04:45 +08:00
github-actions[bot]
c8d5f577a1 chore: update registry index 2026-05-13 03:35:47 +00:00
7c82c4aea5 chore: update onebot registry index version 2026-05-13 11:35:24 +08:00
5491ad1747 refactor(versioning): decouple module release versions
- bump parent and external module parent POMs to 1.0.0
- make runtime, interaction API, ctl, and external modules use explicit versions
- centralize internal dependency versions with dedicated properties
- keep framework and core on the shared runtime 0.5.0 line
- prepare partnerctl and module releases for independent versioning
2026-05-13 11:33:34 +08:00
1be6ed0198 chore: update idea settings 2026-05-13 10:19:55 +08:00
01bfc3ee18 feat(partnerctl-fetch): support fetching raw data via https proxy 2026-05-13 10:16:18 +08:00
2d45adf8c3 feat(partnerctl-module): load external modules from remote registry index 2026-05-12 23:34:35 +08:00
707fddda79 fix(release): checkout repository before creating ctl release 2026-05-11 18:16:40 +08:00
20 changed files with 388 additions and 58 deletions

View File

@@ -130,6 +130,11 @@ jobs:
echo "Ctl release tag: ${TAG}"
echo "Ctl release version: ${VERSION}"
- name: Checkout release source
uses: actions/checkout@v4
with:
ref: ${{ steps.release.outputs.tag }}
- name: Download release artifacts
uses: actions/download-artifact@v4
with:

37
.idea/misc.xml generated
View File

@@ -1,28 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<list size="21">
<list size="22">
<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="2" class="java.lang.String" itemvalue="picocli.CommandLine.Command" />
<item index="3" class="java.lang.String" itemvalue="picocli.CommandLine.Mixin" />
<item index="4" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.Capability" />
<item index="5" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityCore" />
<item index="6" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityMethod" />
<item index="7" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CoordinateManager" />
<item index="8" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.Coordinated" />
<item index="9" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.component.annotation.Init" />
<item index="10" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AfterExecute" />
<item index="11" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AgentRunningModule" />
<item index="12" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AgentSubModule" />
<item index="13" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.BeforeExecute" />
<item index="14" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.Init" />
<item index="15" class="java.lang.String" itemvalue="work.slhaf.partner.api.capability.annotation.CapabilityMethod" />
<item index="16" class="java.lang.String" itemvalue="work.slhaf.partner.api.capability.annotation.CoordinateManager" />
<item index="17" class="java.lang.String" itemvalue="work.slhaf.partner.api.register.capability.annotation.Capability" />
<item index="18" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityCore" />
<item index="19" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityMethod" />
<item index="20" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.component.annotation.AgentComponent" />
<item index="4" class="java.lang.String" itemvalue="picocli.CommandLine.Option" />
<item index="5" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.Capability" />
<item index="6" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityCore" />
<item index="7" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityMethod" />
<item index="8" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CoordinateManager" />
<item index="9" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.Coordinated" />
<item index="10" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.component.annotation.Init" />
<item index="11" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AfterExecute" />
<item index="12" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AgentRunningModule" />
<item index="13" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AgentSubModule" />
<item index="14" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.BeforeExecute" />
<item index="15" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.Init" />
<item index="16" class="java.lang.String" itemvalue="work.slhaf.partner.api.capability.annotation.CapabilityMethod" />
<item index="17" class="java.lang.String" itemvalue="work.slhaf.partner.api.capability.annotation.CoordinateManager" />
<item index="18" class="java.lang.String" itemvalue="work.slhaf.partner.api.register.capability.annotation.Capability" />
<item index="19" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityCore" />
<item index="20" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityMethod" />
<item index="21" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.component.annotation.AgentComponent" />
</list>
<writeAnnotations>
<writeAnnotation name="work.slhaf.partner.api.agent.factory.capability.annotation.InjectCapability" />

View File

@@ -6,10 +6,11 @@
<parent>
<groupId>work.slhaf.partner</groupId>
<artifactId>partner</artifactId>
<version>0.5.0</version>
<version>1.0.0</version>
</parent>
<artifactId>partner-core</artifactId>
<version>0.9.0-preview</version>
<dependencies>
<dependency>
@@ -20,7 +21,7 @@
<dependency>
<groupId>work.slhaf.partner</groupId>
<artifactId>partner-framework</artifactId>
<version>0.5.0</version>
<version>${partner.runtime.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.nd4j/nd4j-api -->
<dependency>

View File

@@ -6,22 +6,23 @@
<parent>
<groupId>work.slhaf.partner</groupId>
<artifactId>partner-external-modules</artifactId>
<version>0.5.0</version>
<version>1.0.0</version>
</parent>
<artifactId>partner-onebot-adapter</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>work.slhaf.partner</groupId>
<artifactId>partner-core</artifactId>
<version>${project.version}</version>
<version>${partner.runtime.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>work.slhaf.partner</groupId>
<artifactId>partner-framework</artifactId>
<version>${project.version}</version>
<version>${partner.runtime.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>

View File

@@ -6,10 +6,11 @@
<parent>
<groupId>work.slhaf.partner</groupId>
<artifactId>partner</artifactId>
<version>0.5.0</version>
<version>1.0.0</version>
</parent>
<artifactId>partner-external-modules</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>

View File

@@ -6,10 +6,11 @@
<parent>
<groupId>work.slhaf.partner</groupId>
<artifactId>partner</artifactId>
<version>0.5.0</version>
<version>1.0.0</version>
</parent>
<artifactId>partner-framework</artifactId>
<version>0.9.0-preview</version>
<dependencies>
<dependency>
@@ -86,7 +87,7 @@
<dependency>
<groupId>work.slhaf.partner</groupId>
<artifactId>partner-interaction-api</artifactId>
<version>0.5.0</version>
<version>${partner.interaction-api.version}</version>
</dependency>
</dependencies>

View File

@@ -6,10 +6,11 @@
<parent>
<groupId>work.slhaf.partner</groupId>
<artifactId>partner</artifactId>
<version>0.5.0</version>
<version>1.0.0</version>
</parent>
<artifactId>partner-interaction-api</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>

View File

@@ -6,10 +6,11 @@
<parent>
<groupId>work.slhaf.partner</groupId>
<artifactId>partner</artifactId>
<version>0.5.0</version>
<version>1.0.0</version>
</parent>
<artifactId>partnerctl</artifactId>
<version>1.0.1</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
@@ -44,7 +45,7 @@
<dependency>
<groupId>work.slhaf.partner</groupId>
<artifactId>partner-interaction-api</artifactId>
<version>0.5.0</version>
<version>${partner.interaction-api.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
@@ -122,6 +123,7 @@
<buildArg>-H:+ReportExceptionStackTraces</buildArg>
<buildArg>--initialize-at-build-time=kotlin.DeprecationLevel</buildArg>
<buildArg>-H:IncludeResourceBundles=i18n.messages</buildArg>
<buildArg>-H:IncludeLocales=zh-CN</buildArg>
</buildArgs>
</configuration>
</plugin>

View File

@@ -2,13 +2,12 @@ package work.slhaf.partner.ctl.commands
import kotlinx.serialization.json.*
import picocli.CommandLine
import work.slhaf.partner.ctl.commands.InitCommand.InstallChoice.BUILD_FROM_SOURCE
import work.slhaf.partner.ctl.commands.InitCommand.InstallChoice.DOWNLOAD_JAR
import work.slhaf.partner.ctl.commands.data.GatewayConfig
import work.slhaf.partner.ctl.commands.data.OpenAiCompatible
import work.slhaf.partner.ctl.commands.data.ProviderConfig
import work.slhaf.partner.ctl.commands.init.buildFromSource
import work.slhaf.partner.ctl.commands.init.configureExternalGateway
import work.slhaf.partner.ctl.commands.init.configureOpenAiCompatible
import work.slhaf.partner.ctl.commands.init.configureWebSocketGateway
import work.slhaf.partner.ctl.commands.init.*
import work.slhaf.partner.ctl.i18n.I18n.text
import work.slhaf.partner.ctl.support.CommandInterrupted
import work.slhaf.partner.ctl.support.inheritCommand
@@ -176,11 +175,15 @@ class InitCommand : Runnable {
val installChoice = prompt.select(
label = text("init.install.method.label"),
choices = listOf(Choice(text("init.install.method.buildFromSource"), InstallChoice.BUILD_FROM_SOURCE))
choices = listOf(
Choice(text("init.install.method.buildFromSource"), BUILD_FROM_SOURCE),
Choice(text("init.install.method.downloadFromRelease"), DOWNLOAD_JAR)
)
)
when (installChoice) {
InstallChoice.BUILD_FROM_SOURCE -> buildFromSource(home, prompt)
BUILD_FROM_SOURCE -> buildFromSource(home, prompt)
DOWNLOAD_JAR -> downloadFromRelease(home, prompt)
}
}
@@ -348,7 +351,8 @@ class InitCommand : Runnable {
}
private enum class InstallChoice {
BUILD_FROM_SOURCE
BUILD_FROM_SOURCE,
DOWNLOAD_JAR
}
private enum class ModelProviderChoice(val display: String) {

View File

@@ -64,7 +64,8 @@ fun configureExternalGateway(home: Path, prompt: Prompt, manifest: ModuleManifes
text("configure.gateway.external.details.buildCommand") to manifest.source.buildCommand.joinToString(" "),
text("configure.gateway.external.details.artifact") to "${manifest.source.artifactDirectory}/${manifest.source.artifactPattern}",
text("configure.gateway.external.details.installTarget") to manifest.install.target,
text("configure.gateway.external.details.configTarget") to (manifest.config?.target ?: text("configure.gateway.external.details.noConfig")),
text("configure.gateway.external.details.configTarget") to (manifest.config?.target
?: text("configure.gateway.external.details.noConfig")),
),
)
@@ -138,18 +139,43 @@ private fun askField(prompt: Prompt, field: Field): JsonElement? {
}
}
@Suppress("KotlinConstantConditions")
private fun validateFieldValue(field: Field, value: String): String? {
if (value.isBlank() && !field.required) return null
return when (field.type) {
FieldType.STRING -> null
FieldType.INT -> value.toIntOrNull()?.let { null } ?: text("configure.field.error.int", field.label)
FieldType.NUMBER -> value.toDoubleOrNull()?.let { null } ?: text("configure.field.error.number", field.label)
FieldType.BOOLEAN -> value.toBooleanStrictOrNull()?.let { null } ?: text("configure.field.error.boolean", field.label)
FieldType.RAW_JSON -> runCatching { Json.parseToJsonElement(value) }
.exceptionOrNull()
?.let { text("configure.field.error.rawJson", field.label) }
FieldType.INT -> {
if (value.toIntOrNull() == null) {
text("configure.field.error.int", field.label)
} else {
null
}
}
FieldType.NUMBER -> {
if (value.toDoubleOrNull() == null) {
text("configure.field.error.number", field.label)
} else {
null
}
}
FieldType.BOOLEAN -> {
if (value.toBooleanStrictOrNull() == null) {
text("configure.field.error.boolean", field.label)
} else {
null
}
}
FieldType.RAW_JSON -> {
val result = runCatching { Json.parseToJsonElement(value) }.exceptionOrNull()
if (result == null) {
text("configure.field.error.rawJson", field.label)
} else {
null
}
}
}
}

View File

@@ -1,15 +1,19 @@
package work.slhaf.partner.ctl.commands.init
import work.slhaf.partner.ctl.i18n.I18n.text
import work.slhaf.partner.ctl.support.SourceBuildInstallSpec
import work.slhaf.partner.ctl.support.buildAndInstallFromSource
import work.slhaf.partner.ctl.support.downloadTo
import work.slhaf.partner.ctl.support.registryIndex
import work.slhaf.partner.ctl.ui.Prompt
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.exists
import kotlin.io.path.isDirectory
import kotlin.io.path.name
private const val PARTNER_REPO_URL = "https://gitea.slhaf.work/slhaf/Partner.git"
private const val PARTNER_REPO_URL = "https://github.com/slhaf/Partner.git"
fun buildFromSource(home: Path, prompt: Prompt) {
buildAndInstallFromSource(
@@ -40,3 +44,41 @@ private fun findLargestJar(directory: Path): Path? {
.orElse(null)
}
}
fun downloadFromRelease(home: Path, prompt: Prompt) {
prompt.info(text("init.install.method.downloadFromRelease.startDownloading"))
val path = home.resolve("resources/partner-core.jar").toAbsolutePath().normalize()
downloadTo(registryIndex.partner.latestRelease.url, path) { downloaded, total ->
if (total != null && total > 0) {
val percent = downloaded * 100 / total
updateLine(
text(
"init.install.method.downloadFromRelease.progress.percent",
percent
)
)
} else {
updateLine(
text(
"init.install.method.downloadFromRelease.progress.size",
downloaded / 1024
)
)
}
}
finishLine(text("init.install.method.downloadFromRelease.done"))
if (!path.exists()) {
throw IllegalStateException("Unable to find downloaded partner release at $path")
}
prompt.success(text("init.install.method.downloadFromRelease.success"))
}
fun updateLine(text: String) {
print("\r\u001B[2K$text")
System.out.flush()
}
fun finishLine(text: String) {
updateLine(text)
println()
}

View File

@@ -1,10 +1,20 @@
package work.slhaf.partner.ctl.support
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
private const val registryUrl = "https://raw.githubusercontent.com/slhaf/Partner/refs/heads/master/registry"
private const val indexUrl = "$registryUrl/index.json"
val registryIndex = run {
Json.decodeFromString<RegistryIndex>(fetchText(indexUrl))
}
private fun loadModules(): Set<ModuleManifest> {
// TODO: 待实现具体加载逻辑
return emptySet()
return registryIndex.externalModules.map { indexItem ->
val manifestStr = fetchText("$registryUrl/${indexItem.registryRef}")
return@map Json.decodeFromString<ModuleManifest>(manifestStr)
}.toSet()
}
fun loadAvailableGateway(): Set<ModuleManifest> {
@@ -91,4 +101,36 @@ enum class FieldType {
NUMBER,
BOOLEAN,
RAW_JSON,
}
}
@Serializable
data class RegistryIndex(
val partner: PartnerIndex,
val externalModules: List<ModulesIndexItem>
)
@Serializable
data class PartnerIndex(
val latestBuildable: Buildable,
val latestRelease: Release
) {
@Serializable
data class Buildable(
val url: String,
val ref: String
)
@Serializable
data class Release(
val url: String,
val version: String
)
}
@Serializable
data class ModulesIndexItem(
val name: String,
val version: String,
val withGateway: Boolean,
val registryRef: String
)

View File

@@ -0,0 +1,135 @@
package work.slhaf.partner.ctl.support
import java.io.IOException
import java.net.InetSocketAddress
import java.net.ProxySelector
import java.net.URI
import java.net.http.*
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import java.time.Duration
import kotlin.io.path.isDirectory
private val httpClient: HttpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(20))
.followRedirects(HttpClient.Redirect.NORMAL)
.apply {
proxySelectorFromEnv()?.let(::proxy)
}
.build()
private fun proxySelectorFromEnv(): ProxySelector? {
val proxyText = System.getenv("HTTPS_PROXY")
?: System.getenv("https_proxy")
?: return null
val proxyUri = URI.create(proxyText)
val host = proxyUri.host
?: throw IllegalArgumentException("Invalid HTTPS_PROXY host: $proxyText")
val port = proxyUri.port
if (port == -1) {
throw IllegalArgumentException("HTTPS_PROXY must include port: $proxyText")
}
return ProxySelector.of(InetSocketAddress(host, port))
}
fun fetchText(url: String): String {
var lastError: Exception? = null
repeat(3) { attempt ->
try {
val request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(60))
.header("User-Agent", "partnerctl")
.GET()
.build()
val response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofString()
)
if (response.statusCode() !in 200..299) {
throw IOException("Failed to fetch $url: HTTP ${response.statusCode()}")
}
return response.body()
} catch (e: HttpTimeoutException) {
lastError = e
} catch (e: HttpConnectTimeoutException) {
lastError = e
} catch (e: IOException) {
lastError = e
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
throw IOException("Interrupted while fetching $url", e)
}
if (attempt < 2) {
Thread.sleep(500L * (attempt + 1))
}
}
throw IOException("Failed to fetch $url after retries", lastError)
}
fun downloadTo(
url: String,
targetPath: Path,
onProgress: (downloaded: Long, total: Long?) -> Unit = { _, _ -> }
) {
if (targetPath.isDirectory()) {
throw IllegalArgumentException("Target path must be a file")
}
val targetPath = targetPath.toAbsolutePath().normalize()
val targetFile = targetPath.toFile()
val temp = Files.createTempFile(
"${targetFile.name}-${System.currentTimeMillis()}", ".${targetFile.extension}.download"
)
try {
val request = HttpRequest.newBuilder()
.uri(URI.create(url))
.GET()
.build()
val response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofInputStream()
)
if (response.statusCode() !in 200..299) {
throw IllegalStateException("Failed to download from $url: HTTP ${response.statusCode()}")
}
val totalBytes = response.headers()
.firstValue("Content-Length")
.orElse(null)
?.toLongOrNull()
response.body().use { input ->
Files.newOutputStream(temp).use { output ->
val buffer = ByteArray(8192)
var downloaded = 0L
while (true) {
val read = input.read(buffer)
if (read < 0) break
output.write(buffer, 0, read)
downloaded += read
onProgress(downloaded, totalBytes)
}
}
}
Files.move(temp, targetPath, StandardCopyOption.REPLACE_EXISTING)
} catch (e: Exception) {
Files.deleteIfExists(temp)
throw e
}
}

View File

@@ -31,6 +31,12 @@ init.home.overwrite.refuseBroadDirectory=Refuse to overwrite suspiciously broad
init.install.section=Install Partner
init.install.method.label=Choose an installation method
init.install.method.buildFromSource=Build Partner from source
init.install.method.downloadFromRelease=Download Partner release
init.install.method.downloadFromRelease.startDownloading=Downloading Partner release...
init.install.method.downloadFromRelease.success=Partner release downloaded successfully.
init.install.method.downloadFromRelease.progress.percent=Downloading Partner release... {0}%
init.install.method.downloadFromRelease.progress.size=Downloading Partner release... {0} KB
init.install.method.downloadFromRelease.done=Downloading Partner release... Done
init.gateway.section=Configure Gateway
init.gateway.select.label=Select gateway
init.gateway.websocket.choice=WebSocket Gateway

View File

@@ -31,6 +31,12 @@ init.home.overwrite.refuseBroadDirectory=拒绝覆盖范围过大的目录:{0}
init.install.section=安装 Partner
init.install.method.label=选择安装方式
init.install.method.buildFromSource=从源码构建 Partner
init.install.method.downloadFromRelease=下载 Partner 发布包
init.install.method.downloadFromRelease.startDownloading=正在下载 Partner 发布包...
init.install.method.downloadFromRelease.success=Partner 发布包下载完成。
init.install.method.downloadFromRelease.progress.percent=正在下载 Partner 发布包... {0}%
init.install.method.downloadFromRelease.progress.size=正在下载 Partner 发布包... {0} KB
init.install.method.downloadFromRelease.done=正在下载 Partner 发布包... 完成
init.gateway.section=配置网关
init.gateway.select.label=选择网关
init.gateway.websocket.choice=WebSocket Gateway

View File

@@ -0,0 +1,12 @@
package experimental
import kotlinx.serialization.json.Json
import work.slhaf.partner.ctl.support.RegistryIndex
import work.slhaf.partner.ctl.support.fetchText
fun main() {
val str = fetchText("https://raw.githubusercontent.com/slhaf/Partner/refs/heads/master/registry/index.json")
val index = Json.decodeFromString<RegistryIndex>(str)
println(index)
}

View File

@@ -25,12 +25,54 @@ Partner 分为 `Partner-Framework` 与 `Partner-Core` 两层。前者提供配
## 项目启动
**环境要求**
### 环境要求
**基础运行要求**
- JDK 21
- Maven 3.x
### 手动准备环境并启动
**仅在从源码构建 Partner Runtime 或外部模块时需要**
- Maven 3.x
- Git
### 推荐方式PartnerCtl
`PartnerCtl` 用于完成 Partner 的首次初始化、运行时安装与启动管理。相比手动准备运行目录和配置文件,使用它可以更快完成最小可运行环境的搭建。
#### 初始化
```bash
partnerctl init
```
初始化流程会引导完成:
- 选择 `PARTNER_HOME`
- 安装 Partner Runtime
- 从源码构建
- 下载发布版 jar
- 配置 Gateway
- 配置模型 Provider
- 可选立即启动 Partner
#### 启动
如果初始化完成后未选择立即启动,可执行:
```bash
partnerctl run
```
如需后台运行:
```bash
partnerctl run -d
```
PartnerCtl 默认读取 `PARTNER_HOME` 指定的运行目录;若未设置,则使用 `~/.partner`
### 手动方式:从源码构建并启动
#### 克隆项目并构建
@@ -162,4 +204,3 @@ Partner/
## License
暂未指定。

View File

@@ -5,7 +5,7 @@
<groupId>work.slhaf.partner</groupId>
<artifactId>partner</artifactId>
<version>0.5.0</version>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
@@ -26,6 +26,9 @@
<!-- 推荐仓库默认不跳测试;本地需要时再 -DskipTests=true -->
<skipTests>false</skipTests>
<partner.runtime.version>0.9.0-preview</partner.runtime.version>
<partner.interaction-api.version>1.0.0</partner.interaction-api.version>
</properties>
<dependencies>

View File

@@ -2,17 +2,17 @@
"partner": {
"latestBuildable": {
"url": "https://github.com/slhaf/Partner.git",
"ref": "buildable/0.5.0"
"ref": "buildable/0.9.0-preview"
},
"latestRelease": {
"url": "https://github.com/slhaf/Partner/releases/download/release-core%2F0.5.0/partner-core-0.5.0.jar",
"version": "0.5.0"
"url": "https://github.com/slhaf/Partner/releases/download/release-core%2F0.9.0-preview/partner-core-0.9.0-preview.jar",
"version": "0.9.0-preview"
}
},
"externalModules": [
{
"name": "OneBot Adapter",
"version": "0.5.0",
"version": "1.0.0",
"withGateway": true,
"registryRef": "modules/onebot-adapter.json"
}

View File

@@ -1,7 +1,7 @@
{
"id": "onebot_channel",
"name": "OneBot Adapter",
"version": "0.5.0",
"version": "1.0.0",
"withGateway": true,
"description": "OneBot v11 reverse WebSocket gateway adapter for Partner. It accepts reverse WebSocket connections from a OneBot implementation and converts private message events into Partner input events.",
"source": {