39 Commits

Author SHA1 Message Date
11aae1a353 feat(impression): introduce entity relation and impression model 2026-05-18 00:01:00 +08:00
e5dcb49028 chore: update gitignore 2026-05-17 21:07:21 +08:00
70a94d9c30 refactor(cognition): move context classes into context package 2026-05-16 21:57:29 +08:00
ef096e76b3 refactor(state): optimize StateValue building methods 2026-05-15 23:07:40 +08:00
ed743521ec chore: rename cognition core into context core 2026-05-15 16:16:44 +08:00
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
68589a21fc chore(release): add partnerctl release workflow 2026-05-11 15:18:15 +08:00
e8228d0cc1 fix(ci): make partnerctl native test workflow dispatchable 2026-05-11 11:33:00 +08:00
cc99e565f0 chore(ci): add partnerctl native build test workflow 2026-05-11 11:29:33 +08:00
github-actions[bot]
718271417d chore(registry): update latest core release to release-core/0.5.0 2026-05-11 03:15:42 +00:00
04ee38726b chore(release): use release-core tag workflow 2026-05-11 11:09:10 +08:00
github-actions[bot]
4a00e65868 chore(registry): update latest core release to rel-v0.5.0 2026-05-11 03:00:38 +00:00
6c3c08b5d5 chore: unify workflows name style 2026-05-11 10:24:29 +08:00
ef5a332f17 chore(release): add partner core release workflow 2026-05-11 10:05:43 +08:00
github-actions[bot]
9acabca40e chore(registry): update latest buildable to buildable/0.5.0 2026-05-10 15:28:36 +00:00
b654090a6e feat(git): add latest buildable update workflow 2026-05-10 23:18:07 +08:00
github-actions[bot]
c9a7343b30 chore: update registry index 2026-05-10 14:27:29 +00:00
6250b480e0 chore: add onebot adapter registry manifest 2026-05-10 22:27:13 +08:00
github-actions[bot]
63e8cc314a chore: update registry index 2026-05-10 14:22:35 +00:00
451f83e14d fix(module-registry): resolve manifest paths from registry in index update script 2026-05-10 22:22:12 +08:00
2b7e6718d9 fix(git): stage registry/index.json in update workflow 2026-05-10 22:10:41 +08:00
ed806c668a feat(git): add module index updating workflow 2026-05-10 22:08:47 +08:00
da381c3adf refactor(partnerctl-module): add version attribute to manifest 2026-05-10 21:40:20 +08:00
1cbff98b36 chore(git): remove legacy gitea sync workflow 2026-05-10 21:36:12 +08:00
95a30f147c chore: add initial module registry structure 2026-05-10 21:32:06 +08:00
0ea8dfc7d9 fix(core): correct logback log level fallback syntax (#1) 2026-05-09 14:51:28 +08:00
63 changed files with 1497 additions and 228 deletions

137
.github/workflows/release-core.yml vendored Normal file
View File

@@ -0,0 +1,137 @@
name: Release Partner Core
on:
workflow_dispatch:
inputs:
tag:
description: "Core release tag, for example release-core/0.5.0"
required: true
type: string
push:
tags:
- "release-core/*"
permissions:
contents: write
jobs:
release-core:
runs-on: ubuntu-latest
steps:
- name: Resolve release metadata
id: release
shell: bash
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG="${{ inputs.tag }}"
else
TAG="${GITHUB_REF_NAME}"
fi
VERSION="${TAG#release-core/}"
VERSION="${VERSION#v}"
ENCODED_TAG="${TAG//\//%2F}"
ASSET_NAME="partner-core-${VERSION}.jar"
ASSET_URL="https://github.com/slhaf/Partner/releases/download/${ENCODED_TAG}/${ASSET_NAME}"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "asset_name=${ASSET_NAME}" >> "$GITHUB_OUTPUT"
echo "asset_url=${ASSET_URL}" >> "$GITHUB_OUTPUT"
echo "Core release tag: ${TAG}"
echo "Core release version: ${VERSION}"
echo "Core release asset: ${ASSET_NAME}"
- name: Checkout release source
uses: actions/checkout@v4
with:
ref: ${{ steps.release.outputs.tag }}
- name: Set up Java 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
- name: Build Partner Core
run: |
mvn -B -DskipTests=true -pl Partner-Core -am package
- name: Prepare release artifact
shell: bash
run: |
mkdir -p dist
SOURCE_JAR="Partner-Core/target/partner-core-${{ steps.release.outputs.version }}.jar"
if [ ! -f "$SOURCE_JAR" ]; then
echo "Expected artifact not found: $SOURCE_JAR"
echo "Available Partner-Core target files:"
find Partner-Core/target -maxdepth 1 -type f -name "*.jar" -print
exit 1
fi
cp "$SOURCE_JAR" "dist/${{ steps.release.outputs.asset_name }}"
ls -lh dist
- name: Create GitHub Release
shell: bash
run: |
if gh release view "${{ steps.release.outputs.tag }}" >/dev/null 2>&1; then
echo "Release ${{ steps.release.outputs.tag }} already exists."
exit 1
fi
gh release create "${{ steps.release.outputs.tag }}" \
"dist/${{ steps.release.outputs.asset_name }}" \
--title "Partner Core ${{ steps.release.outputs.version }}" \
--notes "Partner Core ${{ steps.release.outputs.version }}"
env:
GH_TOKEN: ${{ github.token }}
- name: Checkout master for registry update
uses: actions/checkout@v4
with:
ref: master
path: registry-worktree
- name: Update latestRelease
working-directory: registry-worktree
run: |
python3 - <<'PY'
import json
from pathlib import Path
version = "${{ steps.release.outputs.version }}"
url = "${{ steps.release.outputs.asset_url }}"
index_path = Path("registry/index.json")
index = json.loads(index_path.read_text(encoding="utf-8"))
index["partner"]["latestRelease"] = {
"url": url,
"version": version
}
index_path.write_text(
json.dumps(index, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8"
)
PY
- name: Commit registry update
working-directory: registry-worktree
run: |
if git diff --quiet registry/index.json; then
echo "No latestRelease changes."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add registry/index.json
git commit -m "chore(registry): update latest core release to ${{ steps.release.outputs.tag }}"
git push origin HEAD:master

161
.github/workflows/release-ctl.yml vendored Normal file
View File

@@ -0,0 +1,161 @@
name: Release PartnerCtl
on:
workflow_dispatch:
inputs:
tag:
description: "Ctl release tag, for example release-ctl/0.5.0"
required: true
type: string
push:
tags:
- "release-ctl/*"
permissions:
contents: write
jobs:
build:
name: Build ${{ matrix.platform }}
strategy:
fail-fast: false
matrix:
include:
- platform: linux-x64
runner: ubuntu-latest
binaryPath: PartnerCtl/target/partnerctl
assetSuffix: linux-x64
- platform: linux-arm64
runner: ubuntu-24.04-arm
binaryPath: PartnerCtl/target/partnerctl
assetSuffix: linux-arm64
- platform: windows-x64
runner: windows-latest
binaryPath: PartnerCtl/target/partnerctl.exe
assetSuffix: windows-x64.exe
runs-on: ${{ matrix.runner }}
steps:
- name: Resolve release metadata
id: release
shell: bash
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG="${{ inputs.tag }}"
else
TAG="${GITHUB_REF_NAME}"
fi
VERSION="${TAG#release-ctl/}"
VERSION="${VERSION#v}"
ASSET_NAME="partnerctl-${VERSION}-${{ matrix.assetSuffix }}"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "asset_name=${ASSET_NAME}" >> "$GITHUB_OUTPUT"
echo "Ctl release tag: ${TAG}"
echo "Ctl release version: ${VERSION}"
echo "Ctl release asset: ${ASSET_NAME}"
- name: Checkout release source
uses: actions/checkout@v4
with:
ref: ${{ steps.release.outputs.tag }}
- name: Set up GraalVM 21
uses: graalvm/setup-graalvm@v1
with:
distribution: graalvm-community
java-version: "21"
github-token: ${{ github.token }}
- name: Build partnerctl native image
run: mvn -B -DskipTests=true -pl PartnerCtl -am package native:compile
- name: Prepare release artifact
shell: bash
run: |
mkdir -p dist
BINARY_PATH="${{ matrix.binaryPath }}"
ASSET_PATH="dist/${{ steps.release.outputs.asset_name }}"
if [ ! -f "$BINARY_PATH" ]; then
echo "Expected native binary not found: $BINARY_PATH"
echo "Available PartnerCtl target files:"
find PartnerCtl/target -maxdepth 2 -type f -print
exit 1
fi
cp "$BINARY_PATH" "$ASSET_PATH"
if [[ "${{ matrix.platform }}" == linux-* ]]; then
chmod +x "$ASSET_PATH"
fi
ls -lh dist
- name: Upload release artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.release.outputs.asset_name }}
path: dist/${{ steps.release.outputs.asset_name }}
if-no-files-found: error
retention-days: 1
release:
name: Create GitHub Release
needs: build
runs-on: ubuntu-latest
steps:
- name: Resolve release metadata
id: release
shell: bash
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG="${{ inputs.tag }}"
else
TAG="${GITHUB_REF_NAME}"
fi
VERSION="${TAG#release-ctl/}"
VERSION="${VERSION#v}"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
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:
path: dist
merge-multiple: true
- name: Show release artifacts
run: |
ls -lh dist
- name: Create GitHub Release
shell: bash
run: |
if gh release view "${{ steps.release.outputs.tag }}" >/dev/null 2>&1; then
echo "Release ${{ steps.release.outputs.tag }} already exists."
exit 1
fi
gh release create "${{ steps.release.outputs.tag }}" \
dist/* \
--title "PartnerCtl ${{ steps.release.outputs.version }}" \
--notes "PartnerCtl ${{ steps.release.outputs.version }}"
env:
GH_TOKEN: ${{ github.token }}

View File

@@ -1,36 +0,0 @@
name: Sync from Gitea
# 1. 给 GITHUB_TOKEN 开写权限
permissions:
contents: write
on:
schedule:
- cron: '*/30 * * * *'
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: 配置 Git 用户
run: |
git config --global user.name "Gitea Sync Bot"
git config --global user.email "slhafzjw@slhaf.work"
- name: 关闭全局 SSL 校验
run: git config --global http.sslVerify false
- name: Clone from Gitea (mirror)
run: |
git clone --mirror \
https://${{ secrets.GITEA_USER }}:${{ secrets.GITEA_TOKEN }}@${{ secrets.GITEA_URL }} \
gitea-mirror
- name: Push to GitHub
run: |
cd gitea-mirror
# 明确推到名为 "github" 的 remote
git remote add github \
https://${{ github.repository_owner }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
git push --mirror github

View File

@@ -0,0 +1,137 @@
name: Test PartnerCtl Native Build
on:
workflow_dispatch:
inputs:
platform:
description: "Target platform to test"
required: true
default: "windows-x64"
type: choice
options:
- windows-x64
- linux-x64
- linux-arm64
permissions:
contents: read
jobs:
test-linux-x64:
name: Test linux-x64
if: ${{ inputs.platform == 'linux-x64' }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up GraalVM 21
uses: graalvm/setup-graalvm@v1
with:
distribution: graalvm-community
java-version: "21"
github-token: ${{ github.token }}
- name: Build partnerctl native image
run: mvn -B -DskipTests=true -pl PartnerCtl -am package native:compile
- name: Check Linux output
shell: bash
run: |
if [ ! -f "PartnerCtl/target/partnerctl" ]; then
echo "Expected native binary not found: PartnerCtl/target/partnerctl"
echo "Available PartnerCtl target files:"
find PartnerCtl/target -maxdepth 2 -type f -print
exit 1
fi
chmod +x PartnerCtl/target/partnerctl
./PartnerCtl/target/partnerctl --help
- name: Upload test artifact
uses: actions/upload-artifact@v4
with:
name: partnerctl-linux-x64-test
path: PartnerCtl/target/partnerctl
if-no-files-found: error
retention-days: 1
test-linux-arm64:
name: Test linux-arm64
if: ${{ inputs.platform == 'linux-arm64' }}
runs-on: ubuntu-24.04-arm
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up GraalVM 21
uses: graalvm/setup-graalvm@v1
with:
distribution: graalvm-community
java-version: "21"
github-token: ${{ github.token }}
- name: Build partnerctl native image
run: mvn -B -DskipTests=true -pl PartnerCtl -am package native:compile
- name: Check Linux output
shell: bash
run: |
if [ ! -f "PartnerCtl/target/partnerctl" ]; then
echo "Expected native binary not found: PartnerCtl/target/partnerctl"
echo "Available PartnerCtl target files:"
find PartnerCtl/target -maxdepth 2 -type f -print
exit 1
fi
chmod +x PartnerCtl/target/partnerctl
./PartnerCtl/target/partnerctl --help
- name: Upload test artifact
uses: actions/upload-artifact@v4
with:
name: partnerctl-linux-arm64-test
path: PartnerCtl/target/partnerctl
if-no-files-found: error
retention-days: 1
test-windows-x64:
name: Test windows-x64
if: ${{ inputs.platform == 'windows-x64' }}
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up GraalVM 21
uses: graalvm/setup-graalvm@v1
with:
distribution: graalvm-community
java-version: "21"
github-token: ${{ github.token }}
- name: Build partnerctl native image
run: mvn -B -DskipTests=true -pl PartnerCtl -am package native:compile
- name: Check Windows output
shell: pwsh
run: |
if (!(Test-Path "PartnerCtl/target/partnerctl.exe")) {
Write-Host "Expected native binary not found: PartnerCtl/target/partnerctl.exe"
Write-Host "Available PartnerCtl target files:"
Get-ChildItem -Recurse PartnerCtl/target | Select-Object FullName
throw "partnerctl.exe not found"
}
.\PartnerCtl\target\partnerctl.exe --help
- name: Upload test artifact
uses: actions/upload-artifact@v4
with:
name: partnerctl-windows-x64-test
path: PartnerCtl/target/partnerctl.exe
if-no-files-found: error
retention-days: 1

View File

@@ -0,0 +1,70 @@
name: Update Latest Buildable
on:
workflow_dispatch:
inputs:
ref:
description: "Buildable ref, for example buildable/v0.5.0"
required: true
type: string
push:
tags:
- "buildable/*"
permissions:
contents: write
jobs:
update-latest-buildable:
runs-on: ubuntu-latest
steps:
- name: Resolve buildable ref
id: buildable
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "ref=${{ inputs.ref }}" >> "$GITHUB_OUTPUT"
else
echo "ref=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
fi
- name: Checkout master
uses: actions/checkout@v4
with:
ref: master
- name: Update latestBuildable
run: |
python3 - <<'PY'
import json
from pathlib import Path
ref = "${{ steps.buildable.outputs.ref }}"
index_path = Path("registry/index.json")
index = json.loads(index_path.read_text(encoding="utf-8"))
index["partner"]["latestBuildable"] = {
"url": "https://github.com/slhaf/Partner.git",
"ref": ref
}
index_path.write_text(
json.dumps(index, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8"
)
PY
- name: Commit registry update
run: |
if git diff --quiet registry/index.json; then
echo "No latestBuildable changes."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add registry/index.json
git commit -m "chore(registry): update latest buildable to ${{ steps.buildable.outputs.ref }}"
git push

View File

@@ -0,0 +1,38 @@
name: Update Module Index
on:
push:
branches:
- master
paths:
- "registry/modules/*.json"
- "registry/index.json"
- "scripts/update_module_index.py"
- "update-module-index.yml"
permissions:
contents: write
jobs:
update-registry-index:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Update module index
run: python3 scripts/update_module_index.py
- name: Commit updated index
run: |
if git diff --quiet registry/index.json; then
echo "No module index changes"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add registry/index.json
git commit -m "chore: update registry index"
git push

1
.gitignore vendored
View File

@@ -61,3 +61,4 @@ build/
# Maven / build outputs
dependency-reduced-pom.xml
/.backup/

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

@@ -50,70 +50,70 @@ final class ActionPoolStateCodec {
}
private static StateValue.Obj encodeExecutableAction(ExecutableAction action) {
Map<String, StateValue> actionMap = new LinkedHashMap<>();
actionMap.put("kind", StateValue.str(action instanceof SchedulableExecutableAction ? "schedulable" : "immediate"));
actionMap.put("uuid", StateValue.str(action.getUuid()));
actionMap.put("source", StateValue.str(action.getSource()));
actionMap.put("reason", StateValue.str(action.getReason()));
actionMap.put("description", StateValue.str(action.getDescription()));
actionMap.put("status", StateValue.str(action.getStatus().name()));
actionMap.put("tendency", StateValue.str(action.getTendency()));
actionMap.put("executing_stage", StateValue.num(action.getExecutingStage()));
Map<String, Object> actionMap = new LinkedHashMap<>();
actionMap.put("kind", action instanceof SchedulableExecutableAction ? "schedulable" : "immediate");
actionMap.put("uuid", action.getUuid());
actionMap.put("source", action.getSource());
actionMap.put("reason", action.getReason());
actionMap.put("description", action.getDescription());
actionMap.put("status", action.getStatus().name());
actionMap.put("tendency", action.getTendency());
actionMap.put("executing_stage", action.getExecutingStage());
String result = resolveExecutableResult(action);
if (result != null) {
actionMap.put("result", StateValue.str(result));
actionMap.put("result", result);
}
if (action instanceof SchedulableExecutableAction schedulableAction) {
actionMap.put("schedule_type", StateValue.str(schedulableAction.getScheduleType().name()));
actionMap.put("schedule_content", StateValue.str(schedulableAction.getScheduleContent()));
actionMap.put("enabled", StateValue.bool(schedulableAction.getEnabled()));
actionMap.put("schedule_histories", StateValue.arr(encodeScheduleHistories(schedulableAction)));
actionMap.put("schedule_type", schedulableAction.getScheduleType().name());
actionMap.put("schedule_content", schedulableAction.getScheduleContent());
actionMap.put("enabled", schedulableAction.getEnabled());
actionMap.put("schedule_histories", encodeScheduleHistories(schedulableAction));
}
List<StateValue> chainStates = action.getActionChain().entrySet().stream()
List<StateValue.Obj> chainStates = action.getActionChain().entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.<StateValue>map(entry -> {
Map<String, StateValue> stageMap = new LinkedHashMap<>();
stageMap.put("stage", StateValue.num(entry.getKey()));
.map(entry -> {
Map<String, Object> stageMap = new LinkedHashMap<>();
stageMap.put("stage", entry.getKey());
String stageDescription = action.getStageDescriptions().get(entry.getKey());
if (stageDescription != null && !stageDescription.isBlank()) {
stageMap.put("description", StateValue.str(stageDescription));
stageMap.put("description", stageDescription);
}
stageMap.put("actions", StateValue.arr(entry.getValue().stream()
.map(metaAction -> (StateValue) encodeMetaAction(metaAction))
.toList()));
stageMap.put("actions", entry.getValue().stream()
.map(ActionPoolStateCodec::encodeMetaAction)
.toList());
return StateValue.obj(stageMap);
}).toList();
actionMap.put("action_chain", StateValue.arr(chainStates));
actionMap.put("action_chain", chainStates);
actionMap.put("history", StateValue.arr(encodeHistoryStages(action.getHistory())));
actionMap.put("history", encodeHistoryStages(action.getHistory()));
return StateValue.obj(actionMap);
}
private static StateValue.Obj encodeMetaAction(MetaAction metaAction) {
Map<String, StateValue> metaMap = new LinkedHashMap<>();
metaMap.put("name", StateValue.str(metaAction.getName()));
metaMap.put("io", StateValue.bool(metaAction.getIo()));
Map<String, Object> metaMap = new LinkedHashMap<>();
metaMap.put("name", metaAction.getName());
metaMap.put("io", metaAction.getIo());
if (metaAction.getLauncher() != null) {
metaMap.put("launcher", StateValue.str(metaAction.getLauncher()));
metaMap.put("launcher", metaAction.getLauncher());
}
metaMap.put("type", StateValue.str(metaAction.getType().name()));
metaMap.put("location", StateValue.str(metaAction.getLocation()));
metaMap.put("params_json", StateValue.str(JSONObject.toJSONString(metaAction.getParams())));
metaMap.put("result_status", StateValue.str(metaAction.getResult().getStatus().name()));
metaMap.put("type", metaAction.getType().name());
metaMap.put("location", metaAction.getLocation());
metaMap.put("params_json", JSONObject.toJSONString(metaAction.getParams()));
metaMap.put("result_status", metaAction.getResult().getStatus().name());
if (metaAction.getResult().getData() != null) {
metaMap.put("result_data", StateValue.str(metaAction.getResult().getData()));
metaMap.put("result_data", metaAction.getResult().getData());
}
return StateValue.obj(metaMap);
}
private static StateValue.Obj encodeHistoryAction(HistoryAction historyAction) {
Map<String, StateValue> historyMap = new LinkedHashMap<>();
historyMap.put("action_key", StateValue.str(historyAction.actionKey()));
historyMap.put("description", StateValue.str(historyAction.description()));
historyMap.put("result", StateValue.str(historyAction.result()));
Map<String, Object> historyMap = new LinkedHashMap<>();
historyMap.put("action_key", historyAction.actionKey());
historyMap.put("description", historyAction.description());
historyMap.put("result", historyAction.result());
return StateValue.obj(historyMap);
}
@@ -288,26 +288,26 @@ final class ActionPoolStateCodec {
return restored;
}
private static List<StateValue> encodeHistoryStages(Map<Integer, ? extends List<HistoryAction>> historyMap) {
private static List<StateValue.Obj> encodeHistoryStages(Map<Integer, ? extends List<HistoryAction>> historyMap) {
return historyMap.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.<StateValue>map(entry -> {
Map<String, StateValue> stageMap = new LinkedHashMap<>();
stageMap.put("stage", StateValue.num(entry.getKey()));
stageMap.put("actions", StateValue.arr(entry.getValue().stream()
.map(historyAction -> (StateValue) encodeHistoryAction(historyAction))
.toList()));
.map(entry -> {
Map<String, Object> stageMap = new LinkedHashMap<>();
stageMap.put("stage", entry.getKey());
stageMap.put("actions", entry.getValue().stream()
.map(ActionPoolStateCodec::encodeHistoryAction)
.toList());
return StateValue.obj(stageMap);
}).toList();
}
private static List<StateValue> encodeScheduleHistories(SchedulableExecutableAction schedulableAction) {
private static List<StateValue.Obj> encodeScheduleHistories(SchedulableExecutableAction schedulableAction) {
return schedulableAction.getScheduleHistories().stream()
.<StateValue>map(scheduleHistory -> {
Map<String, StateValue> historyMap = new LinkedHashMap<>();
historyMap.put("end_time", StateValue.str(scheduleHistory.getEndTime().toString()));
historyMap.put("result", StateValue.str(scheduleHistory.getResult()));
historyMap.put("history", StateValue.arr(encodeHistoryStages(scheduleHistory.getHistory())));
.map(scheduleHistory -> {
Map<String, Object> historyMap = new LinkedHashMap<>();
historyMap.put("end_time", scheduleHistory.getEndTime().toString());
historyMap.put("result", scheduleHistory.getResult());
historyMap.put("history", encodeHistoryStages(scheduleHistory.getHistory()));
return StateValue.obj(historyMap);
})
.toList();

View File

@@ -1,6 +1,7 @@
package work.slhaf.partner.core.cognition;
import org.w3c.dom.Element;
import work.slhaf.partner.core.cognition.context.ContextWorkspace;
import work.slhaf.partner.framework.agent.factory.capability.annotation.Capability;
import work.slhaf.partner.framework.agent.model.pojo.Message;

View File

@@ -1,4 +1,4 @@
package work.slhaf.partner.core.cognition;
package work.slhaf.partner.core.cognition.context;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
@@ -27,7 +27,7 @@ import java.util.concurrent.locks.ReentrantLock;
@Slf4j
@CapabilityCore(value = "cognition")
public class CognitionCore implements StateSerializable {
public class ContextCore implements StateSerializable {
private static final String RECENT_CHAT_MESSAGE_NOTES = """
消息格式:
@@ -58,7 +58,7 @@ public class CognitionCore implements StateSerializable {
private final ContextWorkspace contextWorkspace = new ContextWorkspace();
public CognitionCore() {
public ContextCore() {
register();
}
@@ -200,13 +200,12 @@ public class CognitionCore implements StateSerializable {
public @NotNull State convert() {
State state = new State();
List<StateValue.Obj> convertedMessageList = chatMessages.stream().map(message -> {
Map<String, StateValue> convertedMap = Map.of(
"role", StateValue.str(message.roleValue()),
"content", StateValue.str(message.getContent())
);
return StateValue.obj(convertedMap);
}).toList();
List<StateValue.Obj> convertedMessageList = chatMessages.stream()
.map(message -> StateValue.obj(Map.of(
"role", message.roleValue(),
"content", message.getContent()
)))
.toList();
state.append("chat_messages", StateValue.arr(convertedMessageList));
return state;

View File

@@ -1,4 +1,4 @@
package work.slhaf.partner.core.cognition
package work.slhaf.partner.core.cognition.context
import com.alibaba.fastjson2.JSONObject
import org.w3c.dom.Document

View File

@@ -1,4 +1,4 @@
package work.slhaf.partner.core.cognition
package work.slhaf.partner.core.cognition.context
import org.w3c.dom.Document
import work.slhaf.partner.framework.agent.model.pojo.Message

View File

@@ -0,0 +1,109 @@
package work.slhaf.partner.core.cognition.impression
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
class Entity @JvmOverloads constructor(
val uuid: String = UUID.randomUUID().toString(),
private val relations: MutableMap<String, MutableMap<String, Double>> = mutableMapOf(),
private val impressions: MutableMap<String, ImpressionData> = mutableMapOf()
) {
private val impressionLock = ReentrantLock()
private val relationLock = ReentrantLock()
@JvmOverloads
fun updateRelation(
target: String,
relation: String,
strength: Double = 1.0
) = relationLock.withLock {
relations.computeIfAbsent(target) { mutableMapOf() }[relation] = strength
}
@JvmOverloads
fun updateImpression(
impression: String,
newImpression: String? = null,
strength: Double = 1.0
): ImpressionData = impressionLock.withLock {
if (newImpression == null) {
impressions.computeIfAbsent(impression) { ImpressionData(strength) }
.also { it.confidence = strength }
} else {
impressions.remove(impression)
ImpressionData(strength).also {
impressions[newImpression] = it
}
}
}
fun removeImpression(impression: String) = impressionLock.withLock {
impressions.remove(impression)
}
@JvmOverloads
fun removeRelation(
target: String,
relation: String? = null
) = relationLock.withLock {
if (relation == null) {
relations.remove(target)
} else {
relations[target]?.remove(relation)
if (relations[target].isNullOrEmpty()) {
relations.remove(target)
}
}
}
fun showRelations(): Set<RelationView> = relationLock.withLock {
relations.map {
RelationView(
it.key,
it.value.toMap()
)
}.toSet()
}
fun showImpressions(embeddingModel: String): Set<ImpressionView> = impressionLock.withLock {
impressions.map {
ImpressionView(
it.key,
it.value.confidence,
it.value.getVector(embeddingModel)
)
}.toSet()
}
data class ImpressionData(
var confidence: Double
) {
private val vectors: ConcurrentHashMap<String, DoubleArray> = ConcurrentHashMap()
fun updateVector(
embeddingModel: String,
vector: DoubleArray
) {
vectors[embeddingModel] = vector
}
fun getVector(embeddingModel: String): DoubleArray? {
return vectors[embeddingModel]?.copyOf()
}
}
data class RelationView(
val target: String,
val relations: Map<String, Double>
)
@Suppress("ArrayInDataClass")
data class ImpressionView(
val impression: String,
val confidence: Double,
val vector: DoubleArray?
)
}

View File

@@ -175,8 +175,7 @@ public class MemoryCore implements StateSerializable {
State state = new State();
state.append("memory_session_id", StateValue.str(memorySessionId));
List<StateValue.Str> unitOverview = memoryUnits.keySet().stream()
.map(StateValue::str)
List<String> unitOverview = memoryUnits.keySet().stream()
.toList();
state.append("memory_unit_uuid_set", StateValue.arr(unitOverview));
return state;

View File

@@ -95,25 +95,23 @@ public class MemoryUnit implements StateSerializable {
state.append("id", StateValue.str(id));
state.append("update_timestamp", StateValue.num(timestamp));
List<StateValue.Obj> convertedMessageList = conversationMessages.stream().map(message -> {
Map<String, StateValue> convertedMap = Map.of(
"role", StateValue.str(message.roleValue()),
"content", StateValue.str(message.getContent())
);
return StateValue.obj(convertedMap);
}).toList();
List<StateValue.Obj> convertedMessageList = conversationMessages.stream()
.map(message -> StateValue.obj(Map.of(
"role", message.roleValue(),
"content", message.getContent()
)))
.toList();
state.append("conversation_messages", StateValue.arr(convertedMessageList));
List<StateValue.Obj> convertedSliceList = slices.stream().map(slice -> {
Map<String, StateValue> convertedMap = Map.of(
"id", StateValue.str(slice.getId()),
"start_index", StateValue.num(slice.getStartIndex()),
"end_index", StateValue.num(slice.getEndIndex()),
"summary", StateValue.str(slice.getSummary()),
"created_timestamp", StateValue.num(slice.getTimestamp())
);
return StateValue.obj(convertedMap);
}).toList();
List<StateValue.Obj> convertedSliceList = slices.stream()
.map(slice -> StateValue.obj(Map.of(
"id", slice.getId(),
"start_index", slice.getStartIndex(),
"end_index", slice.getEndIndex(),
"summary", slice.getSummary(),
"created_timestamp", slice.getTimestamp()
)))
.toList();
state.append("memory_slices", StateValue.arr(convertedSliceList));
return state;
}

View File

@@ -6,9 +6,9 @@ import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.core.memory.MemoryCapability;
import work.slhaf.partner.core.memory.pojo.MemorySlice;
import work.slhaf.partner.core.memory.pojo.MemoryUnit;

View File

@@ -12,7 +12,11 @@ import work.slhaf.partner.core.action.entity.MetaAction;
import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.action.entity.intervention.InterventionType;
import work.slhaf.partner.core.action.entity.intervention.MetaIntervention;
import work.slhaf.partner.core.cognition.*;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.CommunicationBlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.core.cognition.context.ContextWorkspace;
import work.slhaf.partner.framework.agent.exception.AgentRuntimeException;
import work.slhaf.partner.framework.agent.exception.ExceptionReporterHandler;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;

View File

@@ -5,7 +5,7 @@ import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule;
import work.slhaf.partner.framework.agent.model.ActivateModel;

View File

@@ -6,7 +6,7 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element;
import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule;
import work.slhaf.partner.framework.agent.model.ActivateModel;

View File

@@ -6,9 +6,9 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element;
import work.slhaf.partner.core.action.entity.*;
import work.slhaf.partner.core.action.entity.intervention.MetaIntervention;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.ContextWorkspace;
import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.core.cognition.context.ContextWorkspace;
import work.slhaf.partner.module.StateHintContent;
import work.slhaf.partner.module.action.executor.entity.HistoryAction;

View File

@@ -6,7 +6,7 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element;
import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule;
import work.slhaf.partner.framework.agent.model.ActivateModel;

View File

@@ -8,9 +8,9 @@ import org.w3c.dom.Element;
import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.action.entity.*;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.framework.agent.exception.AgentRuntimeException;
import work.slhaf.partner.framework.agent.exception.ExceptionReporterHandler;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;

View File

@@ -6,10 +6,10 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element;
import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.ResolvedContext;
import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.core.cognition.context.ResolvedContext;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule;
import work.slhaf.partner.framework.agent.factory.component.annotation.Init;

View File

@@ -2,7 +2,7 @@ package work.slhaf.partner.module.action.planner.extractor;
import org.jetbrains.annotations.NotNull;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.framework.agent.exception.AgentRuntimeException;
import work.slhaf.partner.framework.agent.exception.ModuleExecutionException;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;

View File

@@ -3,8 +3,8 @@ package work.slhaf.partner.module
import org.w3c.dom.Document
import org.w3c.dom.Element
import work.slhaf.partner.common.base.Block
import work.slhaf.partner.core.cognition.CommunicationBlockContent
import work.slhaf.partner.core.cognition.ContextBlock
import work.slhaf.partner.core.cognition.context.CommunicationBlockContent
import work.slhaf.partner.core.cognition.context.ContextBlock
import work.slhaf.partner.framework.agent.model.pojo.Message
abstract class TaskBlock @JvmOverloads constructor(

View File

@@ -5,7 +5,11 @@ import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import work.slhaf.partner.core.cognition.*;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.CommunicationBlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.core.cognition.context.ResolvedContext;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule;
import work.slhaf.partner.framework.agent.factory.component.annotation.Init;

View File

@@ -9,9 +9,9 @@ import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.action.entity.Schedulable;
import work.slhaf.partner.core.action.entity.StateAction;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.core.memory.MemoryCapability;
import work.slhaf.partner.core.memory.pojo.MemorySlice;
import work.slhaf.partner.core.memory.pojo.MemoryUnit;

View File

@@ -69,8 +69,8 @@ final class MemoryRuntimeStateCodec {
List<StateValue.Obj> dateIndexStates = dateIndex.entries().entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(entry -> StateValue.obj(Map.of(
"date", StateValue.str(entry.getKey().toString()),
"refs", StateValue.arr(encodeSliceRefs(entry.getValue()))
"date", entry.getKey().toString(),
"refs", encodeSliceRefs(entry.getValue())
)))
.toList();
state.append("date_index", StateValue.arr(dateIndexStates));
@@ -82,8 +82,8 @@ final class MemoryRuntimeStateCodec {
TopicMemoryIndex.TopicTreeNode topicNode,
List<StateValue.Obj> topicStates) {
topicStates.add(StateValue.obj(Map.of(
"topic_path", StateValue.str(path),
"bindings", StateValue.arr(encodeTopicBindings(topicNode.bindings()))
"topic_path", path,
"bindings", encodeTopicBindings(topicNode.bindings())
)));
for (Map.Entry<String, TopicMemoryIndex.TopicTreeNode> childEntry : topicNode.children().entrySet()) {
collectTopicStates(path + "->" + childEntry.getKey(), childEntry.getValue(), topicStates);
@@ -93,18 +93,16 @@ final class MemoryRuntimeStateCodec {
private List<StateValue> encodeTopicBindings(List<TopicMemoryIndex.TopicBinding> bindings) {
return bindings.stream()
.map(binding -> (StateValue) StateValue.obj(Map.of(
"unit_id", StateValue.str(binding.sliceRef().getUnitId()),
"slice_id", StateValue.str(binding.sliceRef().getSliceId()),
"timestamp", StateValue.num(binding.timestamp()),
"unit_id", binding.sliceRef().getUnitId(),
"slice_id", binding.sliceRef().getSliceId(),
"timestamp", binding.timestamp(),
"activation_profile", StateValue.obj(Map.of(
"activation_weight", StateValue.num(binding.activationProfile().getActivationWeight()),
"diffusion_weight", StateValue.num(binding.activationProfile().getDiffusionWeight()),
"activation_weight", binding.activationProfile().getActivationWeight(),
"diffusion_weight", binding.activationProfile().getDiffusionWeight(),
"context_independence_weight",
StateValue.num(binding.activationProfile().getContextIndependenceWeight())
binding.activationProfile().getContextIndependenceWeight()
)),
"related_topic_paths", StateValue.arr(binding.relatedTopicPaths().stream()
.map(StateValue::str)
.toList())
"related_topic_paths", binding.relatedTopicPaths()
)))
.toList();
}
@@ -156,8 +154,8 @@ final class MemoryRuntimeStateCodec {
private List<StateValue> encodeSliceRefs(List<SliceRef> refs) {
return refs.stream()
.map(ref -> (StateValue) StateValue.obj(Map.of(
"unit_id", StateValue.str(ref.getUnitId()),
"slice_id", StateValue.str(ref.getSliceId())
"unit_id", ref.getUnitId(),
"slice_id", ref.getSliceId()
)))
.toList();
}

View File

@@ -7,9 +7,9 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element;
import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule;
import work.slhaf.partner.framework.agent.factory.component.annotation.InjectModule;

View File

@@ -7,7 +7,7 @@ import org.w3c.dom.Element;
import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule;
import work.slhaf.partner.framework.agent.factory.component.annotation.Init;

View File

@@ -5,7 +5,7 @@ import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule;
import work.slhaf.partner.framework.agent.factory.component.annotation.InjectModule;

View File

@@ -3,10 +3,10 @@ package work.slhaf.partner.module.perceive;
import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.CommunicationBlockContent;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.CommunicationBlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.core.perceive.PerceiveCapability;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule;

View File

@@ -4,9 +4,9 @@ import kotlin.Unit;
import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule;
import work.slhaf.partner.runtime.PartnerRunningFlowContext;

View File

@@ -3,9 +3,9 @@ package work.slhaf.partner.runtime.exception;
import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.framework.agent.exception.AgentException;
import work.slhaf.partner.framework.agent.exception.ExceptionReport;
import work.slhaf.partner.framework.agent.exception.ExceptionReporter;

View File

@@ -1,7 +1,7 @@
<configuration>
<property name="PARTNER_HOME" value="${PARTNER_HOME:-${user.home}/.partner}"/>
<property name="PARTNER_LOG_DIR" value="${PARTNER_HOME}/state/trace/log"/>
<property name="LOG_LEVEL" value="${PARTNER_LOG_LEVEL}:-${partner.log.level:-INFO}"/>
<property name="LOG_LEVEL" value="${PARTNER_LOG_LEVEL:-${partner.log.level:-INFO}}"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>

View File

@@ -3,6 +3,8 @@ package work.slhaf.partner.core.cognition;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.core.cognition.context.ContextCore;
import work.slhaf.partner.framework.agent.model.pojo.Message;
import java.nio.file.Path;
@@ -20,15 +22,15 @@ class CognitionCoreTest {
@Test
void shouldRenderRecentChatMessagesWithWrapperAndNotes() {
CognitionCore cognitionCore = new CognitionCore();
cognitionCore.getChatMessages().addAll(List.of(
ContextCore contextCore = new ContextCore();
contextCore.getChatMessages().addAll(List.of(
new Message(Message.Character.USER, "[[USER]: user-1]: hello"),
new Message(Message.Character.ASSISTANT, "[NOT_REPLIED]: wait"),
new Message(Message.Character.ASSISTANT, "latest message")
));
cognitionCore.refreshRecentChatMessagesContext();
String content = cognitionCore.contextWorkspace()
contextCore.refreshRecentChatMessagesContext();
String content = contextCore.contextWorkspace()
.resolve(List.of(ContextBlock.FocusedDomain.COMMUNICATION))
.encodeToMessage()
.getContent();

View File

@@ -2,6 +2,9 @@ package work.slhaf.partner.core.cognition
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import work.slhaf.partner.core.cognition.context.BlockContent
import work.slhaf.partner.core.cognition.context.ContextBlock
import work.slhaf.partner.core.cognition.context.ContextWorkspace
class ContextWorkspaceTest {

View File

@@ -10,7 +10,7 @@ import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.action.entity.*;
import work.slhaf.partner.core.action.runner.RunnerClient;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextWorkspace;
import work.slhaf.partner.core.cognition.context.ContextWorkspace;
import work.slhaf.partner.framework.agent.support.Result;
import work.slhaf.partner.module.action.executor.entity.ExtractorResult;
import work.slhaf.partner.module.action.executor.entity.HistoryAction;

View File

@@ -3,7 +3,7 @@ package work.slhaf.partner.module.communication;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Element;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextWorkspace;
import work.slhaf.partner.core.cognition.context.ContextWorkspace;
import work.slhaf.partner.framework.agent.model.pojo.Message;
import work.slhaf.partner.runtime.PartnerRunningFlowContext;

View File

@@ -8,6 +8,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.w3c.dom.Element;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.context.ContextWorkspace;
import work.slhaf.partner.core.memory.MemoryCapability;
import work.slhaf.partner.core.memory.pojo.MemorySlice;
import work.slhaf.partner.core.memory.pojo.MemoryUnit;
@@ -65,8 +66,8 @@ class MemoryRuntimeTest {
}
@Override
public work.slhaf.partner.core.cognition.ContextWorkspace contextWorkspace() {
return new work.slhaf.partner.core.cognition.ContextWorkspace();
public ContextWorkspace contextWorkspace() {
return new ContextWorkspace();
}
@Override

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

@@ -148,10 +148,130 @@ sealed interface StateValue {
fun str(value: String) = Str(value)
@JvmStatic
fun arr(value: List<StateValue>) = Arr(value)
fun arr(value: List<*>): Arr {
val visiting = java.util.IdentityHashMap<Any, Unit>()
return Arr(convertList(value, visiting))
}
@JvmStatic
fun obj(value: Map<String, StateValue>) = Obj(value)
fun obj(value: Map<String, *>): Obj {
val visiting = java.util.IdentityHashMap<Any, Unit>()
return Obj(convertMap(value, visiting))
}
private fun convertValue(
value: Any?,
visiting: java.util.IdentityHashMap<Any, Unit>
): StateValue {
return when (value) {
null -> error("StateValue does not support null")
is StateValue -> normalizeStateValue(value, visiting)
is String -> Str(value)
is Number -> Num(value)
is Boolean -> Bool(value)
is List<*> -> Arr(convertList(value, visiting))
is Map<*, *> -> Obj(convertGenericMap(value, visiting))
else -> error("Unsupported state value type: ${value::class.qualifiedName}")
}
}
private fun normalizeStateValue(
value: StateValue,
visiting: java.util.IdentityHashMap<Any, Unit>
): StateValue {
return when (value) {
is Num -> value
is Bool -> value
is Str -> value
is Arr -> Arr(convertStateValueList(value.value, visiting))
is Obj -> Obj(convertStateValueMap(value.value, visiting))
}
}
private fun convertList(
value: List<*>,
visiting: java.util.IdentityHashMap<Any, Unit>
): List<StateValue> {
enterContainer(value, visiting)
try {
return value.map { convertValue(it, visiting) }
} finally {
leaveContainer(value, visiting)
}
}
private fun convertMap(
value: Map<String, *>,
visiting: java.util.IdentityHashMap<Any, Unit>
): Map<String, StateValue> {
enterContainer(value, visiting)
try {
return value.entries.associateTo(LinkedHashMap()) { (key, mapValue) ->
key to convertValue(mapValue, visiting)
}
} finally {
leaveContainer(value, visiting)
}
}
private fun convertGenericMap(
value: Map<*, *>,
visiting: java.util.IdentityHashMap<Any, Unit>
): Map<String, StateValue> {
enterContainer(value, visiting)
try {
return value.entries.associateTo(LinkedHashMap()) { (key, mapValue) ->
check(key is String) {
"StateValue object key must be String, but got: ${key?.let { it::class.qualifiedName }}"
}
key to convertValue(mapValue, visiting)
}
} finally {
leaveContainer(value, visiting)
}
}
private fun convertStateValueList(
value: List<StateValue>,
visiting: java.util.IdentityHashMap<Any, Unit>
): List<StateValue> {
enterContainer(value, visiting)
try {
return value.map { normalizeStateValue(it, visiting) }
} finally {
leaveContainer(value, visiting)
}
}
private fun convertStateValueMap(
value: Map<String, StateValue>,
visiting: java.util.IdentityHashMap<Any, Unit>
): Map<String, StateValue> {
enterContainer(value, visiting)
try {
return value.entries.associateTo(LinkedHashMap()) { (key, mapValue) ->
key to normalizeStateValue(mapValue, visiting)
}
} finally {
leaveContainer(value, visiting)
}
}
private fun enterContainer(
container: Any,
visiting: java.util.IdentityHashMap<Any, Unit>
) {
check(visiting.put(container, Unit) == null) {
"Circular reference detected while constructing StateValue"
}
}
private fun leaveContainer(
container: Any,
visiting: java.util.IdentityHashMap<Any, Unit>
) {
visiting.remove(container)
}
}
}

View File

@@ -0,0 +1,60 @@
package work.slhaf.partner.framework.agent.state
fun main() {
testNormalStateJson()
println()
testCircularReference()
}
private fun testNormalStateJson() {
val nestedMap = linkedMapOf(
"name" to "partner",
"enabled" to true,
"count" to 3,
"tags" to listOf("agent", "runtime", "state-center"),
"meta" to linkedMapOf(
"version" to "0.1.0",
"experimental" to false
)
)
val state = State()
state.append("root", StateValue.obj(nestedMap))
state.append(
"arr",
StateValue.arr(
listOf(
"hello",
123,
true,
linkedMapOf(
"nested" to "value"
)
)
)
)
println("=== normal state ===")
println(state.toString())
}
private fun testCircularReference() {
val cyclicMap = linkedMapOf<String, Any>()
cyclicMap["name"] = "cyclic"
cyclicMap["self"] = cyclicMap
println("=== circular reference ===")
try {
val state = State()
state.append("cyclic", StateValue.obj(cyclicMap))
// 如果前面没有抛错,这里再触发最终 JSON 输出
println(state.toString())
error("Expected circular reference detection, but no exception was thrown.")
} catch (e: IllegalStateException) {
println("circular reference detected as expected:")
println(e.message)
}
}

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> {
@@ -36,6 +46,7 @@ data class ModuleManifest(
/** Human-readable module description shown before installation. */
val description: String = "",
val version: String,
val source: Source,
val install: Install,
val config: Config? = null,
@@ -91,3 +102,35 @@ enum class FieldType {
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>

20
registry/index.json Normal file
View File

@@ -0,0 +1,20 @@
{
"partner": {
"latestBuildable": {
"url": "https://github.com/slhaf/Partner.git",
"ref": "buildable/0.9.0-preview"
},
"latestRelease": {
"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": "1.0.0",
"withGateway": true,
"registryRef": "modules/onebot-adapter.json"
}
]
}

View File

View File

@@ -0,0 +1,58 @@
{
"id": "onebot_channel",
"name": "OneBot Adapter",
"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": {
"url": "https://github.com/slhaf/Partner.git",
"sourceDirName": "Partner",
"buildCommand": [
"mvn",
"-B",
"-DskipTests=true",
"-pl",
"Partner-External-Modules/Partner-Onebot-Adapter",
"-am",
"package"
],
"artifactDirectory": "Partner-External-Modules/Partner-Onebot-Adapter/target",
"artifactPattern": "partner-onebot-adapter-*.jar"
},
"install": {
"target": "resources/module/partner-onebot-adapter.jar"
},
"config": {
"target": "config/gateway.json",
"fields": [
{
"name": "port",
"label": "OneBot reverse WebSocket server port",
"type": "INT",
"default": "29700",
"required": true
},
{
"name": "hostname",
"label": "OneBot reverse WebSocket server hostname",
"type": "STRING",
"default": "127.0.0.1",
"required": true
},
{
"name": "path",
"label": "OneBot reverse WebSocket path",
"type": "STRING",
"default": "/onebot/v11/ws",
"required": true
},
{
"name": "token",
"label": "OneBot access token",
"type": "STRING",
"default": "",
"required": false
}
]
}
}

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env python3
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
INDEX_PATH = ROOT / "registry" / "index.json"
MODULES_DIR = ROOT / "registry" / "modules"
def load_json(path: Path) -> dict:
with path.open("r", encoding="utf-8") as f:
return json.load(f)
def write_json(path: Path, data: dict) -> None:
path.write_text(
json.dumps(data, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
def build_external_modules() -> list[dict]:
entries = []
if not MODULES_DIR.exists():
return entries
for manifest_path in sorted(MODULES_DIR.glob("*.json")):
manifest = load_json(manifest_path)
# 这里按你当前 ModuleManifest 的完整文件结构取字段。
# version 如果 manifest 里暂时没有,可以先默认取 "0.5.0" 或直接要求 manifest 必须有。
name = manifest["name"]
version = manifest["version"]
with_gateway = manifest.get("withGateway", False)
rel_path = manifest_path.relative_to(ROOT / "registry").as_posix()
entries.append(
{
"name": name,
"version": version,
"withGateway": with_gateway,
"registryRef": rel_path,
}
)
return entries
def main() -> None:
index = load_json(INDEX_PATH)
index["externalModules"] = build_external_modules()
write_json(INDEX_PATH, index)
if __name__ == "__main__":
main()