mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
refactor(managed-agent): Split server into Maven modules (#15871)
* refactor(managed-agent): split server into Maven modules Separate contracts, orchestration, persistence, adapters, assembly, and verification while retaining the existing service delivery model. Move tests and build consumers together and document local evidence and pending CI acceptance. Co-authored-by: Qwen-Coder <[email protected]> * refactor(managed-agent): reduce configuration and SDK boilerplate Co-authored-by: Qwen-Coder <[email protected]> * refactor(managed-agent): narrow contracts and simplify test scaffolding Co-authored-by: Qwen-Coder <[email protected]> * fix(managed-agent): repair verification after module migration Resolve shared fixtures and worker paths from the Maven module root, update CI packaging assertions, and align existing test fixtures with the extracted contracts. Restore restricted Workspace constructors and remove redundant test visibility changes without altering inherited MySQL coverage. Co-authored-by: Qwen-Coder <[email protected]> * test(managed-agent): align remaining Harness and OSS fixtures Preserve SDK refusal messages and register the managed OSS adapter at fixture injection points. Keep the existing retry and client shutdown assertions intact. Co-authored-by: Qwen-Coder <[email protected]> * style(managed-agent): order Hosted cleanup Harness imports Co-authored-by: Qwen-Coder <[email protected]> * test(managed-agent): fix recovery smoke fixture and import order Supply the required transcript coverage in the recovery browser fixture and wait for history before testing a complete handoff. Preserve its existing safety and submission assertions. Order the cleanup coordinator test imports for Checkstyle. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]>
This commit is contained in:
1 parent
e3f12f54f9
commit
f3af3dd857
1231 files changed
+17813
-12514
No files matched your search
@@ -871,7 +871,7 @@ describe('trusted-PR lanes moved off the hosted queue (#13245)', () => {
|
||||
assert.match(run('Build Java components'), /-Dmaven\.repo\.local=/);
|
||||
assert.match(
|
||||
run('Build Java components'),
|
||||
/rm -rf -- "\$\{repo\}\/com\/alibaba\/qwencode-sdk" "\$\{repo\}\/com\/alibaba\/qwen-managed-agent-server"/,
|
||||
/rm -rf -- "\$\{repo\}\/com\/alibaba\/qwencode-sdk" "\$\{repo\}\/com\/alibaba\/"qwen-managed-agent-\*/,
|
||||
);
|
||||
assert.match(
|
||||
run('Assemble reference distribution'),
|
||||
|
||||
@@ -1196,7 +1196,7 @@ jobs:
|
||||
PR_NUMBER: '${{ github.event.pull_request.number }}'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
DOCUMENT='packages/managed-agent-server/src/main/resources/openapi/managed-agent-public-api.openapi.json'
|
||||
DOCUMENT='packages/managed-agent-server/contracts/src/main/resources/openapi/managed-agent-public-api.openapi.json'
|
||||
gh api "repos/${REPO}/pulls/${PR_NUMBER}/files?per_page=100" --paginate --jq '.[] | .filename, .previous_filename // empty' > "${RUNNER_TEMP}/contract-pr-files.txt"
|
||||
if ! grep -qxF "${DOCUMENT}" "${RUNNER_TEMP}/contract-pr-files.txt"; then
|
||||
exit 0
|
||||
@@ -1204,7 +1204,7 @@ jobs:
|
||||
BASE_DOCUMENT="${RUNNER_TEMP}/contract-base.json"
|
||||
git fetch --quiet --depth=1 --filter=blob:none origin "refs/heads/${BASE_REF}"
|
||||
if ! git show "FETCH_HEAD:${DOCUMENT}" > "${BASE_DOCUMENT}" 2>/dev/null; then
|
||||
PREVIOUS_DOCUMENT="$(gh api "repos/${REPO}/pulls/${PR_NUMBER}/files?per_page=100" --paginate --jq '.[] | select(.filename == "packages/managed-agent-server/src/main/resources/openapi/managed-agent-public-api.openapi.json") | .previous_filename // empty')"
|
||||
PREVIOUS_DOCUMENT="$(gh api "repos/${REPO}/pulls/${PR_NUMBER}/files?per_page=100" --paginate --jq '.[] | select(.filename == "packages/managed-agent-server/contracts/src/main/resources/openapi/managed-agent-public-api.openapi.json") | .previous_filename // empty')"
|
||||
if [ -n "${PREVIOUS_DOCUMENT}" ]; then
|
||||
git show "FETCH_HEAD:${PREVIOUS_DOCUMENT}" > "${BASE_DOCUMENT}"
|
||||
else
|
||||
|
||||
@@ -3226,7 +3226,7 @@ jobs:
|
||||
uses: 'actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830' # v4.3.0
|
||||
with:
|
||||
path: '${{ runner.temp }}/verify-maven-repo'
|
||||
key: "verify-maven-repo-${{ hashFiles('packages/sdk-java/*/pom.xml', 'packages/managed-agent-server/pom.xml') }}"
|
||||
key: "verify-maven-repo-${{ hashFiles('packages/sdk-java/*/pom.xml', 'packages/managed-agent-server/**/pom.xml') }}"
|
||||
restore-keys: 'verify-maven-repo-'
|
||||
|
||||
- name: 'Install and build PR app'
|
||||
|
||||
@@ -217,7 +217,7 @@ jobs:
|
||||
cache: "${{ runner.environment == 'github-hosted' && 'maven' || '' }}"
|
||||
cache-dependency-path: |-
|
||||
packages/sdk-java/qwencode/pom.xml
|
||||
packages/managed-agent-server/pom.xml
|
||||
packages/managed-agent-server/**/pom.xml
|
||||
settings-path: '${{ runner.temp }}/setup-java-m2'
|
||||
|
||||
- name: 'Set up Maven (self-hosted)'
|
||||
@@ -326,7 +326,7 @@ jobs:
|
||||
# PR's build must never reach the host-shared ~/.m2.
|
||||
repo="${RUNNER_TEMP}/m2-repo"
|
||||
cp -aln "${HOME}/.m2/repository" "${repo}" 2>/dev/null || true
|
||||
rm -rf -- "${repo}/com/alibaba/qwencode-sdk" "${repo}/com/alibaba/qwen-managed-agent-server"
|
||||
rm -rf -- "${repo}/com/alibaba/qwencode-sdk" "${repo}/com/alibaba/"qwen-managed-agent-*
|
||||
MAVEN_ARGS="${MAVEN_ARGS} -Dmaven.repo.local=${repo}"
|
||||
fi
|
||||
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/qwencode/pom.xml -DskipTests -Dgpg.skip=true clean install
|
||||
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
cache: 'maven'
|
||||
cache-dependency-path: |
|
||||
packages/sdk-java/qwencode/pom.xml
|
||||
packages/managed-agent-server/pom.xml
|
||||
packages/managed-agent-server/**/pom.xml
|
||||
|
||||
- name: 'Set up Node.js 22'
|
||||
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
|
||||
@@ -172,8 +172,9 @@ jobs:
|
||||
- name: 'Verify the executable JAR and publication profile'
|
||||
run: |-
|
||||
mvn --batch-mode --no-transfer-progress -f packages/managed-agent-server/pom.xml clean verify -Pcentral-release -Dgpg.skip=true -DforkCount=1 -DreuseForks=false -Dproject.build.outputTimestamp="${BUILD_TIMESTAMP}"
|
||||
runtime_jar="packages/managed-agent-server/target/qwen-managed-agent-server-${CLI_VERSION}-exec.jar"
|
||||
runtime_jar="packages/managed-agent-server/server/target/qwen-managed-agent-server-${CLI_VERSION}-exec.jar"
|
||||
# Older release tags shipped the executable as the main artifact.
|
||||
if [[ ! -f "${runtime_jar}" ]]; then runtime_jar="packages/managed-agent-server/target/qwen-managed-agent-server-${CLI_VERSION}-exec.jar"; fi
|
||||
if [[ ! -f "${runtime_jar}" ]]; then runtime_jar="packages/managed-agent-server/target/qwen-managed-agent-server-${CLI_VERSION}.jar"; fi
|
||||
unzip -p "${runtime_jar}" META-INF/MANIFEST.MF | tr -d '\r' | grep -Fx 'Main-Class: org.springframework.boot.loader.launch.JarLauncher'
|
||||
node .qwen/managed-release-tools/scripts/managed-runtime-release.js --jar "${runtime_jar}" --version "${CLI_VERSION}" --out-dir dist/managed-runtime
|
||||
|
||||
@@ -21,7 +21,7 @@ on:
|
||||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
- 'packages/managed-agent-server/src/main/resources/openapi/managed-agent-public-api.openapi.json'
|
||||
- 'packages/managed-agent-server/contracts/src/main/resources/openapi/managed-agent-public-api.openapi.json'
|
||||
- 'scripts/check-contract-open-prs.js'
|
||||
- 'scripts/contract-version-utils.js'
|
||||
- '.github/workflows/sdk-java-contract-open-prs.yml'
|
||||
|
||||
@@ -17,9 +17,15 @@ on:
|
||||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
- 'packages/sdk-java/*/persistence/src/main/resources/db/migration/**'
|
||||
- 'packages/sdk-java/*/persistence/src/main/java/db/migration/**'
|
||||
- 'packages/sdk-java/*/persistence/src/main/resources/db/managed-schema/**'
|
||||
- 'packages/sdk-java/*/src/main/resources/db/migration/**'
|
||||
- 'packages/sdk-java/*/src/main/java/db/migration/**'
|
||||
- 'packages/sdk-java/*/src/main/resources/db/managed-schema/**'
|
||||
- 'packages/managed-agent-server/persistence/src/main/resources/db/migration/**'
|
||||
- 'packages/managed-agent-server/persistence/src/main/java/db/migration/**'
|
||||
- 'packages/managed-agent-server/persistence/src/main/resources/db/managed-schema/**'
|
||||
- 'packages/managed-agent-server/src/main/resources/db/migration/**'
|
||||
- 'packages/managed-agent-server/src/main/java/db/migration/**'
|
||||
- 'packages/managed-agent-server/src/main/resources/db/managed-schema/**'
|
||||
|
||||
@@ -199,7 +199,7 @@ jobs:
|
||||
cache: "${{ runner.environment == 'github-hosted' && 'maven' || '' }}"
|
||||
cache-dependency-path: |-
|
||||
packages/sdk-java/qwencode/pom.xml
|
||||
packages/managed-agent-server/pom.xml
|
||||
packages/managed-agent-server/**/pom.xml
|
||||
settings-path: '${{ runner.temp }}/setup-java-m2'
|
||||
|
||||
# The Broker process tests spawn the Node fixture worker.
|
||||
@@ -294,7 +294,14 @@ jobs:
|
||||
# Maven runs would rewrite through into the host cache. Drop them
|
||||
# so the install writes fresh inodes.
|
||||
rm -rf -- "${RUNNER_TEMP}/m2-repo/com/alibaba/qwencode-sdk" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-server"
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-parent" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-contracts" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-control-plane" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-runtime-broker" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-persistence" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-adapters" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-server" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-verification"
|
||||
# The suite resolves from the job-local copy above, so it does not
|
||||
# touch host-shared state and must not hold the per-host lock.
|
||||
# On 2026-10-22 run 39771949573's fast tier failed at its
|
||||
@@ -425,7 +432,7 @@ jobs:
|
||||
mkdir -p "${HOME}/.m2/repository" 9>&-
|
||||
cd "${RUNNER_TEMP}/m2-repo"
|
||||
write_back=0
|
||||
timeout 300 find . \( -path ./com/alibaba/qwencode-sdk -o -path ./com/alibaba/qwen-managed-agent-server -o -name '*.lastUpdated' -o -name 'resolver-status.properties' -o -name '_remote.repositories' -o -name '*.tmp' \) -prune -o -type f -links 1 -exec cp -ln --parents -t "${HOME}/.m2/repository" {} + 9>&- || write_back=$?
|
||||
timeout 300 find . \( -path ./com/alibaba/qwencode-sdk -o -path ./com/alibaba/qwen-managed-agent-parent -o -path ./com/alibaba/qwen-managed-agent-contracts -o -path ./com/alibaba/qwen-managed-agent-control-plane -o -path ./com/alibaba/qwen-managed-agent-runtime-broker -o -path ./com/alibaba/qwen-managed-agent-persistence -o -path ./com/alibaba/qwen-managed-agent-adapters -o -path ./com/alibaba/qwen-managed-agent-server -o -path ./com/alibaba/qwen-managed-agent-verification -o -name '*.lastUpdated' -o -name 'resolver-status.properties' -o -name '_remote.repositories' -o -name '*.tmp' \) -prune -o -type f -links 1 -exec cp -ln --parents -t "${HOME}/.m2/repository" {} + 9>&- || write_back=$?
|
||||
if [ "${write_back}" -eq 124 ]; then
|
||||
echo "::warning::Maven cache write-back stopped after 300 s; the gate result above is unaffected"
|
||||
elif [ "${write_back}" -ne 0 ]; then
|
||||
@@ -466,8 +473,8 @@ jobs:
|
||||
path: |
|
||||
packages/sdk-java/qwencode/target/surefire-reports/*.txt
|
||||
packages/sdk-java/qwencode/target/surefire-reports/*.dumpstream
|
||||
packages/managed-agent-server/target/surefire-reports/*.txt
|
||||
packages/managed-agent-server/target/surefire-reports/*.dumpstream
|
||||
packages/managed-agent-server/verification/target/surefire-reports/*.txt
|
||||
packages/managed-agent-server/verification/target/surefire-reports/*.dumpstream
|
||||
if-no-files-found: 'ignore'
|
||||
retention-days: 7
|
||||
|
||||
@@ -776,7 +783,7 @@ jobs:
|
||||
cache: "${{ runner.environment == 'github-hosted' && 'maven' || '' }}"
|
||||
cache-dependency-path: |-
|
||||
packages/sdk-java/qwencode/pom.xml
|
||||
packages/managed-agent-server/pom.xml
|
||||
packages/managed-agent-server/**/pom.xml
|
||||
settings-path: '${{ runner.temp }}/setup-java-m2'
|
||||
|
||||
- name: 'Point Maven at the pool mirror'
|
||||
@@ -927,7 +934,14 @@ jobs:
|
||||
# Maven runs would rewrite through into the host cache. Drop them
|
||||
# so the install writes fresh inodes.
|
||||
rm -rf -- "${RUNNER_TEMP}/m2-repo/com/alibaba/qwencode-sdk" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-server"
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-parent" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-contracts" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-control-plane" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-runtime-broker" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-persistence" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-adapters" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-server" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-verification"
|
||||
# Both Maven runs resolve from the job-local copy above, so they do
|
||||
# not touch host-shared state and must not hold the per-host lock:
|
||||
# a MySQL verify can run for hours under pool contention, and the
|
||||
@@ -1039,7 +1053,7 @@ jobs:
|
||||
mkdir -p "${HOME}/.m2/repository" 9>&-
|
||||
cd "${RUNNER_TEMP}/m2-repo"
|
||||
write_back=0
|
||||
timeout 300 find . \( -path ./com/alibaba/qwencode-sdk -o -path ./com/alibaba/qwen-managed-agent-server -o -name '*.lastUpdated' -o -name 'resolver-status.properties' -o -name '_remote.repositories' -o -name '*.tmp' \) -prune -o -type f -links 1 -exec cp -ln --parents -t "${HOME}/.m2/repository" {} + 9>&- || write_back=$?
|
||||
timeout 300 find . \( -path ./com/alibaba/qwencode-sdk -o -path ./com/alibaba/qwen-managed-agent-parent -o -path ./com/alibaba/qwen-managed-agent-contracts -o -path ./com/alibaba/qwen-managed-agent-control-plane -o -path ./com/alibaba/qwen-managed-agent-runtime-broker -o -path ./com/alibaba/qwen-managed-agent-persistence -o -path ./com/alibaba/qwen-managed-agent-adapters -o -path ./com/alibaba/qwen-managed-agent-server -o -path ./com/alibaba/qwen-managed-agent-verification -o -name '*.lastUpdated' -o -name 'resolver-status.properties' -o -name '_remote.repositories' -o -name '*.tmp' \) -prune -o -type f -links 1 -exec cp -ln --parents -t "${HOME}/.m2/repository" {} + 9>&- || write_back=$?
|
||||
if [ "${write_back}" -eq 124 ]; then
|
||||
echo "::warning::Maven cache write-back stopped after 300 s; the gate result above is unaffected"
|
||||
elif [ "${write_back}" -ne 0 ]; then
|
||||
@@ -1057,10 +1071,10 @@ jobs:
|
||||
with:
|
||||
name: 'sdk-java-test-reports-mysql-integration-java-21'
|
||||
path: |
|
||||
packages/managed-agent-server/target/surefire-reports/*.txt
|
||||
packages/managed-agent-server/target/surefire-reports/*.dumpstream
|
||||
packages/managed-agent-server/target/failsafe-reports/*.txt
|
||||
packages/managed-agent-server/target/failsafe-reports/*.dumpstream
|
||||
packages/managed-agent-server/verification/target/surefire-reports/*.txt
|
||||
packages/managed-agent-server/verification/target/surefire-reports/*.dumpstream
|
||||
packages/managed-agent-server/verification/target/failsafe-reports/*.txt
|
||||
packages/managed-agent-server/verification/target/failsafe-reports/*.dumpstream
|
||||
if-no-files-found: 'ignore'
|
||||
retention-days: 7
|
||||
|
||||
@@ -1188,7 +1202,7 @@ jobs:
|
||||
cache: "${{ runner.environment == 'github-hosted' && 'maven' || '' }}"
|
||||
cache-dependency-path: |-
|
||||
packages/sdk-java/**/pom.xml
|
||||
packages/managed-agent-server/pom.xml
|
||||
packages/managed-agent-server/**/pom.xml
|
||||
settings-path: '${{ runner.temp }}/setup-java-m2'
|
||||
- name: 'Set up Node.js'
|
||||
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
|
||||
@@ -1281,7 +1295,7 @@ jobs:
|
||||
run: 'node scripts/check-failsafe-reports.js hosted packages/managed-agent-server'
|
||||
- name: 'Check Hosted latency measurements'
|
||||
run: |-
|
||||
test -s packages/managed-agent-server/target/hosted-latency-baseline.json
|
||||
test -s packages/managed-agent-server/verification/target/hosted-latency-baseline.json
|
||||
cd integration-tests
|
||||
npx vitest run cli/hosted-latency-baseline.test.ts
|
||||
- name: 'Run Runtime Broker fault gates'
|
||||
@@ -1302,16 +1316,16 @@ jobs:
|
||||
with:
|
||||
name: 'hosted-harness-mysql-reports'
|
||||
path: |
|
||||
packages/managed-agent-server/target/failsafe-reports/
|
||||
packages/managed-agent-server/target/hosted-latency-baseline.json
|
||||
packages/managed-agent-server/target/fg7-cases/
|
||||
packages/managed-agent-server/target/fg8-cases/
|
||||
packages/managed-agent-server/target/fg9-cases/
|
||||
packages/managed-agent-server/target/fg10-cases/
|
||||
packages/managed-agent-server/target/fg11-cases/
|
||||
packages/managed-agent-server/target/fg12-cases/
|
||||
packages/managed-agent-server/target/fg13-cases/
|
||||
packages/managed-agent-server/target/l4-shell-lifecycle/
|
||||
packages/managed-agent-server/verification/target/failsafe-reports/
|
||||
packages/managed-agent-server/verification/target/hosted-latency-baseline.json
|
||||
packages/managed-agent-server/verification/target/fg7-cases/
|
||||
packages/managed-agent-server/verification/target/fg8-cases/
|
||||
packages/managed-agent-server/verification/target/fg9-cases/
|
||||
packages/managed-agent-server/verification/target/fg10-cases/
|
||||
packages/managed-agent-server/verification/target/fg11-cases/
|
||||
packages/managed-agent-server/verification/target/fg12-cases/
|
||||
packages/managed-agent-server/verification/target/fg13-cases/
|
||||
packages/managed-agent-server/verification/target/l4-shell-lifecycle/
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Verify Hosted admission gates'
|
||||
@@ -1362,7 +1376,7 @@ jobs:
|
||||
uses: 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' # v7.0.1
|
||||
with:
|
||||
name: 'o4-mysql-gate-reports'
|
||||
path: 'packages/managed-agent-server/target/failsafe-reports/'
|
||||
path: 'packages/managed-agent-server/verification/target/failsafe-reports/'
|
||||
retention-days: 7
|
||||
|
||||
hosted-failover-e2e:
|
||||
@@ -1391,7 +1405,7 @@ jobs:
|
||||
cache: "${{ runner.environment == 'github-hosted' && 'maven' || '' }}"
|
||||
cache-dependency-path: |-
|
||||
packages/sdk-java/**/pom.xml
|
||||
packages/managed-agent-server/pom.xml
|
||||
packages/managed-agent-server/**/pom.xml
|
||||
settings-path: '${{ runner.temp }}/setup-java-m2'
|
||||
- name: 'Set up Node.js'
|
||||
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
|
||||
@@ -1710,7 +1724,7 @@ jobs:
|
||||
cache: "${{ runner.environment == 'github-hosted' && 'maven' || '' }}"
|
||||
cache-dependency-path: |-
|
||||
packages/sdk-java/**/pom.xml
|
||||
packages/managed-agent-server/pom.xml
|
||||
packages/managed-agent-server/**/pom.xml
|
||||
settings-path: '${{ runner.temp }}/setup-java-m2'
|
||||
- name: 'Point Maven at the pool mirror'
|
||||
if: "${{ runner.environment == 'self-hosted' }}"
|
||||
@@ -1773,7 +1787,14 @@ jobs:
|
||||
# Maven runs would rewrite through into the host cache. Drop them
|
||||
# so the install writes fresh inodes.
|
||||
rm -rf -- "${RUNNER_TEMP}/m2-repo/com/alibaba/qwencode-sdk" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-server"
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-parent" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-contracts" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-control-plane" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-runtime-broker" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-persistence" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-adapters" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-server" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-verification"
|
||||
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/qwencode/pom.xml -DskipTests -Dgpg.skip=true -Dmaven.javadoc.skip=true install
|
||||
- name: 'Cache the RocketMQ distribution'
|
||||
uses: 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9' # v6.1.0
|
||||
@@ -1816,9 +1837,9 @@ jobs:
|
||||
with:
|
||||
name: 'event-transport-broker-gate-reports'
|
||||
path: |-
|
||||
packages/managed-agent-server/target/failsafe-reports/
|
||||
packages/managed-agent-server/target/event-transport-latency*.json
|
||||
packages/managed-agent-server/target/event-transport-latency.jfr
|
||||
packages/managed-agent-server/verification/target/failsafe-reports/
|
||||
packages/managed-agent-server/verification/target/event-transport-latency*.json
|
||||
packages/managed-agent-server/verification/target/event-transport-latency.jfr
|
||||
${{ runner.temp }}/rocketmq/logs/
|
||||
${{ runner.temp }}/rocketmq/home/logs/
|
||||
retention-days: 7
|
||||
@@ -1970,7 +1991,7 @@ jobs:
|
||||
cache: "${{ runner.environment == 'github-hosted' && 'maven' || '' }}"
|
||||
cache-dependency-path: |-
|
||||
packages/sdk-java/**/pom.xml
|
||||
packages/managed-agent-server/pom.xml
|
||||
packages/managed-agent-server/**/pom.xml
|
||||
settings-path: '${{ runner.temp }}/setup-java-m2'
|
||||
- name: 'Point Maven at the pool mirror'
|
||||
if: "${{ runner.environment == 'self-hosted' }}"
|
||||
@@ -2055,7 +2076,14 @@ jobs:
|
||||
# Maven runs would rewrite through into the host cache. Drop them
|
||||
# so the install writes fresh inodes.
|
||||
rm -rf -- "${RUNNER_TEMP}/m2-repo/com/alibaba/qwencode-sdk" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-server"
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-parent" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-contracts" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-control-plane" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-runtime-broker" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-persistence" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-adapters" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-server" \
|
||||
"${RUNNER_TEMP}/m2-repo/com/alibaba/qwen-managed-agent-verification"
|
||||
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/qwencode/pom.xml -DskipTests -Dgpg.skip=true -Dmaven.javadoc.skip=true install
|
||||
- name: 'Install libfaketime'
|
||||
# The clock-skew row moves one node's whole wall clock. A pool host
|
||||
@@ -2152,8 +2180,8 @@ jobs:
|
||||
with:
|
||||
name: 'event-transport-multi-node-gate-reports'
|
||||
path: |-
|
||||
packages/managed-agent-server/target/failsafe-reports/
|
||||
packages/managed-agent-server/target/mq3-multi-node/
|
||||
packages/managed-agent-server/verification/target/failsafe-reports/
|
||||
packages/managed-agent-server/verification/target/mq3-multi-node/
|
||||
${{ runner.temp }}/rocketmq/logs/
|
||||
${{ runner.temp }}/rocketmq/home/logs/
|
||||
retention-days: 7
|
||||
|
||||
@@ -12,7 +12,7 @@ on:
|
||||
branches: ['main']
|
||||
paths:
|
||||
- 'packages/sdk-java/*/pom.xml'
|
||||
- 'packages/managed-agent-server/pom.xml'
|
||||
- 'packages/managed-agent-server/**/pom.xml'
|
||||
- '.github/workflows/verify-maven-repo.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -107,7 +107,14 @@ jobs:
|
||||
repo="${RUNNER_TEMP:?}/verify-maven-repo"
|
||||
STRIP_ARTIFACTS=(
|
||||
qwencode-sdk
|
||||
qwen-managed-agent-parent
|
||||
qwen-managed-agent-contracts
|
||||
qwen-managed-agent-control-plane
|
||||
qwen-managed-agent-runtime-broker
|
||||
qwen-managed-agent-persistence
|
||||
qwen-managed-agent-adapters
|
||||
qwen-managed-agent-server
|
||||
qwen-managed-agent-verification
|
||||
acp-sdk
|
||||
)
|
||||
for artifact in "${STRIP_ARTIFACTS[@]}"; do
|
||||
@@ -118,4 +125,4 @@ jobs:
|
||||
uses: 'actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830' # v4.3.0
|
||||
with:
|
||||
path: '${{ runner.temp }}/verify-maven-repo'
|
||||
key: "verify-maven-repo-${{ hashFiles('packages/sdk-java/*/pom.xml', 'packages/managed-agent-server/pom.xml') }}"
|
||||
key: "verify-maven-repo-${{ hashFiles('packages/sdk-java/*/pom.xml', 'packages/managed-agent-server/**/pom.xml') }}"
|
||||
@@ -42,3 +42,4 @@ packages/core/src/skills/bundled/browser-use/runtime/
|
||||
# next sync. eslint.config.js already ignores packages/mobile-mcp/** for the
|
||||
# same reason; .prettierignore had cua-driver but not its sibling.
|
||||
packages/mobile-mcp/
|
||||
packages/managed-agent-server/**/target/
|
||||
@@ -59,11 +59,11 @@ bin/qwen-managed-agent-admin csi-protection \
|
||||
/absolute/path/registration.json resident-protection resident-binding
|
||||
```
|
||||
|
||||
From a module build, run the same entry from the executable server jar:
|
||||
From a module build, run the same entry from the repository root:
|
||||
|
||||
```bash
|
||||
java -Dloader.main=com.alibaba.qwen.code.managedagent.workspace.runtime.WorkspaceCsiProtectionMain \
|
||||
-cp target/qwen-managed-agent-server-<version>-exec.jar \
|
||||
java -Dloader.main=com.alibaba.qwen.code.managedagent.maintenance.WorkspaceCsiProtectionMain \
|
||||
-cp packages/managed-agent-server/server/target/qwen-managed-agent-server-<version>-exec.jar \
|
||||
org.springframework.boot.loader.launch.PropertiesLauncher \
|
||||
/absolute/path/registration.json resident-protection resident-binding
|
||||
```
|
||||
@@ -121,11 +121,11 @@ bin/qwen-managed-agent-admin resident-retirement \
|
||||
--spring.config.additional-location=file:/absolute/path/application.yaml
|
||||
```
|
||||
|
||||
From a module build, use the executable server jar and the same arguments:
|
||||
From a module build, run from the repository root with the same arguments:
|
||||
|
||||
```bash
|
||||
java -Dloader.main=com.alibaba.qwen.code.managedagent.persistence.ResidentWorkspaceRetirementMain \
|
||||
-cp target/qwen-managed-agent-server-<version>-exec.jar \
|
||||
java -Dloader.main=com.alibaba.qwen.code.managedagent.maintenance.ResidentWorkspaceRetirementMain \
|
||||
-cp packages/managed-agent-server/server/target/qwen-managed-agent-server-<version>-exec.jar \
|
||||
org.springframework.boot.loader.launch.PropertiesLauncher \
|
||||
inspect /absolute/path/retirement.json \
|
||||
--spring.config.additional-location=file:/absolute/path/application.yaml
|
||||
|
||||
@@ -46,11 +46,11 @@ bin/qwen-managed-agent-admin csi-protection \
|
||||
/absolute/path/registration.json resident-protection resident-binding
|
||||
```
|
||||
|
||||
在模块构建中,从可执行的 server jar 运行同一个入口:
|
||||
使用模块构建产物时,在仓库根目录运行同一个入口:
|
||||
|
||||
```bash
|
||||
java -Dloader.main=com.alibaba.qwen.code.managedagent.workspace.runtime.WorkspaceCsiProtectionMain \
|
||||
-cp target/qwen-managed-agent-server-<version>-exec.jar \
|
||||
java -Dloader.main=com.alibaba.qwen.code.managedagent.maintenance.WorkspaceCsiProtectionMain \
|
||||
-cp packages/managed-agent-server/server/target/qwen-managed-agent-server-<version>-exec.jar \
|
||||
org.springframework.boot.loader.launch.PropertiesLauncher \
|
||||
/absolute/path/registration.json resident-protection resident-binding
|
||||
```
|
||||
@@ -103,11 +103,11 @@ bin/qwen-managed-agent-admin resident-retirement \
|
||||
--spring.config.additional-location=file:/absolute/path/application.yaml
|
||||
```
|
||||
|
||||
在模块构建中,使用可执行的 server jar 和相同的参数:
|
||||
使用模块构建产物时,在仓库根目录运行,并使用相同的参数:
|
||||
|
||||
```bash
|
||||
java -Dloader.main=com.alibaba.qwen.code.managedagent.persistence.ResidentWorkspaceRetirementMain \
|
||||
-cp target/qwen-managed-agent-server-<version>-exec.jar \
|
||||
java -Dloader.main=com.alibaba.qwen.code.managedagent.maintenance.ResidentWorkspaceRetirementMain \
|
||||
-cp packages/managed-agent-server/server/target/qwen-managed-agent-server-<version>-exec.jar \
|
||||
org.springframework.boot.loader.launch.PropertiesLauncher \
|
||||
inspect /absolute/path/retirement.json \
|
||||
--spring.config.additional-location=file:/absolute/path/application.yaml
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
[English](2026-10-15-managed-agent-server-layout.md) | [简体中文](2026-10-15-managed-agent-server-layout.zh-CN.md)
|
||||
|
||||
Successor: [Maven module boundaries](2026-10-29-managed-agent-maven-modules.md). This document retains its historical decisions and validation record; the successor defines the current build layout.
|
||||
|
||||
Status: implemented and validated locally. Remote CI and real MySQL/Linux deployment acceptance have not been run for this change.
|
||||
|
||||
## Problem and current state
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
[English](2026-10-15-managed-agent-server-layout.md) | [简体中文](2026-10-15-managed-agent-server-layout.zh-CN.md)
|
||||
|
||||
后继设计:[Maven 多模块边界](2026-10-29-managed-agent-maven-modules.zh-CN.md)。本文保留当时的决策与验证记录;当前构建结构以多模块设计为准。
|
||||
|
||||
状态:已实现并完成本地验证。本次尚未运行远端 CI 或真实 MySQL/Linux 部署验收。
|
||||
|
||||
## 问题与现状
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
[English](2026-10-19-managed-agent-package-boundaries.md) | [简体中文](2026-10-19-managed-agent-package-boundaries.zh-CN.md)
|
||||
|
||||
Successor: [Maven module boundaries](2026-10-29-managed-agent-maven-modules.md). This document retains its historical decisions and validation record; the successor defines the current build layout.
|
||||
|
||||
Status: implemented and verified locally on main base `f37e003d9450`. Remote CI and formal review are tracked on [PR #14710](https://github.com/QwenLM/qwen-code/pull/14710). The optional MySQL, Hosted and Linux fault profiles were not run locally for this package reorganization.
|
||||
|
||||
## Problem and current state
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
[English](2026-10-19-managed-agent-package-boundaries.md) | [简体中文](2026-10-19-managed-agent-package-boundaries.zh-CN.md)
|
||||
|
||||
后继设计:[Maven 多模块边界](2026-10-29-managed-agent-maven-modules.zh-CN.md)。本文保留当时的决策与验证记录;当前构建结构以多模块设计为准。
|
||||
|
||||
状态:已在主干基点 `f37e003d9450` 上实现并完成本地验证。远端 CI 与正式评审状态见 [PR #14710](https://github.com/QwenLM/qwen-code/pull/14710)。本次包调整未在本地运行可选的 MySQL、Hosted 和 Linux 故障专项 profile。
|
||||
|
||||
## 问题与现状
|
||||
|
||||
@@ -36,18 +36,18 @@ That form does not work for an operator who installed the reference distribution
|
||||
|
||||
| Command | Entry point |
|
||||
| ------------------------ | ---------------------------------------------- |
|
||||
| `storage-registration` | `persistence.WorkspaceStorageRegistrationMain` |
|
||||
| `workspace-recovery` | `persistence.WorkspaceRecoveryMain` |
|
||||
| `workspace-migration` | `workspace.runtime.WorkspaceMigrationMain` |
|
||||
| `maintenance-recovery` | `persistence.WorkspaceMaintenanceRecoveryMain` |
|
||||
| `operator-recovery` | `workspace.runtime.WorkspaceRecoveryCommand` |
|
||||
| `csi-registration` | `persistence.WorkspaceCsiRegistrationMain` |
|
||||
| `csi-protection` | `workspace.runtime.WorkspaceCsiProtectionMain` |
|
||||
| `csi-session` | `persistence.WorkspaceCsiSessionMain` |
|
||||
| `csi-runtime` | `persistence.WorkspaceCsiRuntimeMain` |
|
||||
| `csi-retirement` | `persistence.WorkspaceCsiRetirementMain` |
|
||||
| `csi-worker-ack` | `persistence.WorkspaceCsiWorkerAckMain` |
|
||||
| `resident-retirement` | `persistence.ResidentWorkspaceRetirementMain` |
|
||||
| `storage-registration` | `maintenance.WorkspaceStorageRegistrationMain` |
|
||||
| `workspace-recovery` | `maintenance.WorkspaceRecoveryMain` |
|
||||
| `workspace-migration` | `maintenance.WorkspaceMigrationMain` |
|
||||
| `maintenance-recovery` | `maintenance.WorkspaceMaintenanceRecoveryMain` |
|
||||
| `operator-recovery` | `maintenance.WorkspaceRecoveryCommand` |
|
||||
| `csi-registration` | `maintenance.WorkspaceCsiRegistrationMain` |
|
||||
| `csi-protection` | `maintenance.WorkspaceCsiProtectionMain` |
|
||||
| `csi-session` | `maintenance.WorkspaceCsiSessionMain` |
|
||||
| `csi-runtime` | `maintenance.WorkspaceCsiRuntimeMain` |
|
||||
| `csi-retirement` | `maintenance.WorkspaceCsiRetirementMain` |
|
||||
| `csi-worker-ack` | `maintenance.WorkspaceCsiWorkerAckMain` |
|
||||
| `resident-retirement` | `maintenance.ResidentWorkspaceRetirementMain` |
|
||||
| `resolved-configuration` | `preflight.ResolvedConfiguration` |
|
||||
|
||||
All classes are under `com.alibaba.qwen.code.managedagent`.
|
||||
|
||||
@@ -36,18 +36,18 @@ server 有十二个离线维护入口,包括 Workspace storage 注册、Worksp
|
||||
|
||||
| 命令 | 入口 |
|
||||
| ------------------------ | ---------------------------------------------- |
|
||||
| `storage-registration` | `persistence.WorkspaceStorageRegistrationMain` |
|
||||
| `workspace-recovery` | `persistence.WorkspaceRecoveryMain` |
|
||||
| `workspace-migration` | `workspace.runtime.WorkspaceMigrationMain` |
|
||||
| `maintenance-recovery` | `persistence.WorkspaceMaintenanceRecoveryMain` |
|
||||
| `operator-recovery` | `workspace.runtime.WorkspaceRecoveryCommand` |
|
||||
| `csi-registration` | `persistence.WorkspaceCsiRegistrationMain` |
|
||||
| `csi-protection` | `workspace.runtime.WorkspaceCsiProtectionMain` |
|
||||
| `csi-session` | `persistence.WorkspaceCsiSessionMain` |
|
||||
| `csi-runtime` | `persistence.WorkspaceCsiRuntimeMain` |
|
||||
| `csi-retirement` | `persistence.WorkspaceCsiRetirementMain` |
|
||||
| `csi-worker-ack` | `persistence.WorkspaceCsiWorkerAckMain` |
|
||||
| `resident-retirement` | `persistence.ResidentWorkspaceRetirementMain` |
|
||||
| `storage-registration` | `maintenance.WorkspaceStorageRegistrationMain` |
|
||||
| `workspace-recovery` | `maintenance.WorkspaceRecoveryMain` |
|
||||
| `workspace-migration` | `maintenance.WorkspaceMigrationMain` |
|
||||
| `maintenance-recovery` | `maintenance.WorkspaceMaintenanceRecoveryMain` |
|
||||
| `operator-recovery` | `maintenance.WorkspaceRecoveryCommand` |
|
||||
| `csi-registration` | `maintenance.WorkspaceCsiRegistrationMain` |
|
||||
| `csi-protection` | `maintenance.WorkspaceCsiProtectionMain` |
|
||||
| `csi-session` | `maintenance.WorkspaceCsiSessionMain` |
|
||||
| `csi-runtime` | `maintenance.WorkspaceCsiRuntimeMain` |
|
||||
| `csi-retirement` | `maintenance.WorkspaceCsiRetirementMain` |
|
||||
| `csi-worker-ack` | `maintenance.WorkspaceCsiWorkerAckMain` |
|
||||
| `resident-retirement` | `maintenance.ResidentWorkspaceRetirementMain` |
|
||||
| `resolved-configuration` | `preflight.ResolvedConfiguration` |
|
||||
|
||||
所有类都位于 `com.alibaba.qwen.code.managedagent` 包下。
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
# Managed Agent Server Maven modules
|
||||
|
||||
[English](2026-10-29-managed-agent-maven-modules.md) | [简体中文](2026-10-29-managed-agent-maven-modules.zh-CN.md)
|
||||
|
||||
Status: implemented in the working tree; final acceptance is pending CI. The
|
||||
investigation used `523be857db`. This design supersedes the single-module
|
||||
boundary in the [source layout](2026-10-15-managed-agent-server-layout.md) and
|
||||
[Java package design](2026-10-19-managed-agent-package-boundaries.md).
|
||||
|
||||
## Problem and constraints
|
||||
|
||||
The baseline has 391 production Java sources, 614 test Java sources and 18 Maven
|
||||
profiles. Lexical dependency analysis found cycles spanning 83 and 35 production
|
||||
sources. Models import stores, orchestration recognizes concrete JDBC classes,
|
||||
services contain SQL, and repositories call business services and external tools.
|
||||
Configuration, test fixtures, launchers and CI also assume one source tree.
|
||||
|
||||
The change establishes internal compile-time boundaries in one coordinated
|
||||
migration. Modules share one version and release. Java source compatibility may
|
||||
change; repository consumers migrate together. HTTP routes, JSON, configuration
|
||||
keys and defaults, database bytes, transaction boundaries, identity, fencing and
|
||||
recovery behavior remain unchanged. There is no database migration, service split,
|
||||
new product feature, independent module versioning or unrelated large-class
|
||||
decomposition. Product-specific integrations remain outside this application.
|
||||
|
||||
## Modules and dependencies
|
||||
|
||||
The parent is `com.alibaba:qwen-managed-agent-parent`, with `pom` packaging.
|
||||
Production children use `qwen-managed-agent-<directory>`; `server` preserves
|
||||
`com.alibaba:qwen-managed-agent-server`. All children inherit the parent version.
|
||||
|
||||
| Directory | Responsibility | Internal production dependencies |
|
||||
| ---------------- | ------------------------------------------------------------------------------------------ | -------------------------------- |
|
||||
| `contracts` | Interfaces, values, errors, read-only settings and pure protocol rules | None |
|
||||
| `control-plane` | Session, Workspace, task, lifecycle, automation, Channel and extension orchestration | `contracts` |
|
||||
| `runtime-broker` | Runtime bindings, execution, leases, cancellation, draining and recovery | `contracts` |
|
||||
| `persistence` | Control-plane and Broker JDBC/memory stores, schema and transactions | `contracts` |
|
||||
| `adapters` | Harness SDK, physical providers, HTTP clients, OSS, MQ, Git and filesystem implementations | `contracts` |
|
||||
| `server` | HTTP servers, Spring composition, startup, preflight and operator entry points | All other production modules |
|
||||
| `verification` | Existing tests, fixtures, architectural checks and artifact acceptance; never published | All production modules |
|
||||
|
||||
The four implementation modules do not depend on one another. The server injects
|
||||
their implementations through contracts. A production Java package belongs to one
|
||||
artifact. No sibling source directories, aggregate compilation classpath, shading,
|
||||
generic service registry or catch-all common module bypasses these boundaries.
|
||||
|
||||
Contracts may use the existing JSON, validation annotation and Commons libraries.
|
||||
They must not reference Spring, JDBC, the Java SDK or cloud clients, or perform
|
||||
database, network or process operations. Control-plane code may retain Spring
|
||||
components, scheduling and transaction support, but not JDBC or concrete external
|
||||
clients. Broker orchestration remains independent of Spring.
|
||||
|
||||
Separate Maven artifacts make forbidden implementation imports fail during compilation: sibling implementations are absent from each module's compile classpath. Dependency rules and ArchUnit checks supplement that boundary; a single artifact would leave all implementations on the same classpath. The coordinated package migration gives each package one artifact owner without preserving cross-JAR package-private access. This is the agreed internal-isolation scope, with shared versioning and one service delivery.
|
||||
|
||||
Source counts do not determine module ownership. Broker requests, identities, records and deterministic wire validators belong to contracts when multiple modules consume them without I/O. Runtime scheduling and recovery decisions belong to the Broker implementation. A small Broker implementation module is therefore expected; moving its shared protocol rules back would introduce implementation dependencies in consumers.
|
||||
|
||||
## Contract and implementation changes
|
||||
|
||||
Move existing storage, Harness, Runtime, event and object-store interfaces and
|
||||
their shared values into contracts. Extract only interfaces needed at actual
|
||||
module boundaries. Move externally used nested store records with their value
|
||||
semantics; shared models no longer depend on store implementations. Represent
|
||||
application HTTP status as an integer and translate it in the HTTP layer without
|
||||
changing codes, details or causes. Preserve protocol strings, JSON names, nulls,
|
||||
ordering, encodings and digest inputs. Move pure request-identity calculations and
|
||||
provider-kind constants out of JDBC/provider classes.
|
||||
|
||||
`ManagedAgentSettings` supplies read-only views needed outside server. Authentication,
|
||||
internal listeners, the service account, trusted headers and credential-loading
|
||||
state remain server settings without shared contract declarations.
|
||||
`ManagedAgentProperties` remains server-owned and implements those views, retaining
|
||||
binding annotations, duration units, environment access, credential-file reads and
|
||||
startup checks. Consumers receive views or existing scalar values. This introduces
|
||||
neither configuration snapshots nor dynamic refresh.
|
||||
|
||||
The seven transactional default entry points on `AgentStateStore` receive explicit
|
||||
overrides on the JDBC implementation with their existing transaction semantics.
|
||||
Removing the interface annotations must not lose proxy interception on delegating
|
||||
overloads. Existing Spring and native JDBC transaction mechanisms stay distinct.
|
||||
Same-connection operations and package-private locking helpers remain together in
|
||||
persistence; `Connection` does not escape into ordinary repository contracts.
|
||||
Move each existing transaction body as a whole, never as independently committed
|
||||
CRUD operations.
|
||||
|
||||
`NativeCsiExecutionRepository` carries the existing private CSI preparation and
|
||||
batch-read capabilities. The original JDBC binding store implements it; memory
|
||||
stores do not. The Broker consumes the capability instead of concrete JDBC types.
|
||||
Capability discovery never substitutes for transactional admission, original
|
||||
DataSource identity, writer/generation checks or locking. Missing capabilities keep
|
||||
the existing refusal behavior. A neutral store failure preserves genuine-outage
|
||||
classification without exposing JDBC exception types. A Broker operations
|
||||
interface includes the operations consumed outside its implementation module.
|
||||
Convenience overloads without those consumers remain on the implementation only.
|
||||
|
||||
Move recovery queries and SQL-backed evidence into persistence. Inject the
|
||||
existing Channel repository interfaces rather than constructing JDBC repositories
|
||||
inside the service. Separate extension transactions, archive access and business
|
||||
coordination without changing their order. Extract user-message authorization and
|
||||
input policy without moving authorization outside its original transaction or
|
||||
introducing a policy-to-message-store dependency. Git and SSH work cross explicit
|
||||
ports at the same call sites as before.
|
||||
|
||||
Only adapters depend directly on the Java SDK and cloud clients. Map Harness
|
||||
results and synchronous, asynchronous and callback errors into contract values.
|
||||
Keep HTTP, transport, protocol, generation, capability and refusal distinctions,
|
||||
including retry inputs, status, reason and cause. Delegate SDK payload-digest and
|
||||
permission-rule algorithms through narrow ports instead of duplicating them.
|
||||
Workspace scope and authorization wrappers stay in the control plane. Physical
|
||||
provisioning and communication belong to adapters; construction and listeners
|
||||
belong to server composition.
|
||||
|
||||
CSI maintenance retains its durable sequence: lock and save the original attempt,
|
||||
perform the bounded Kubernetes operation, then fence and save the receipt or
|
||||
unknown outcome. Preserve UID/resourceVersion, request bytes and receipt digests.
|
||||
A lost response remains UNKNOWN and does not authorize blind replay. Operator
|
||||
entry points retain their isolated application context and do not start the public
|
||||
server or business schedulers.
|
||||
|
||||
## Build, resources and verification
|
||||
|
||||
Keep JDK 21, Spring Boot 3.5.16 and existing build-plugin versions. The parent
|
||||
manages versions; children declare their own libraries. The reactor builds all
|
||||
modules, with Boot repackage only in `server`. Preserve the executable and operator
|
||||
classifiers and published server coordinate; outputs move to `server/target`.
|
||||
The SDK retains its existing separately installed build prerequisite. Publish the
|
||||
parent and every production dependency at the same version; never publish the
|
||||
verification module.
|
||||
|
||||
Server composition preserves non-servlet embedding, explicitly enabled internal
|
||||
callbacks and standalone serving. Keep named managed data sources, transaction
|
||||
managers, mappers, executors and schedulers separate from host defaults. Restrict
|
||||
component scans and maintain a single owner for closing each resource.
|
||||
|
||||
| Resource | Owner |
|
||||
| --------------------------------------------- | -------------- |
|
||||
| OpenAPI and shared protocol schemas | `contracts` |
|
||||
| CSI native-tool protocol declarations | `contracts` |
|
||||
| V1 database schema | `persistence` |
|
||||
| Application YAML and Spring registration | `server` |
|
||||
| Test fixtures and JUnit service registrations | `verification` |
|
||||
|
||||
Classpath resource names stay unchanged and each resource has one artifact owner.
|
||||
The TypeScript tool-result schema remains authoritative. Update launchers, Docker,
|
||||
distribution/version checks, WebShell contract reads, historical-path fallbacks
|
||||
and CI consumers together. Resource moves do not increment protocol/schema versions.
|
||||
|
||||
Move all existing tests and fixtures to verification, preserving their execution
|
||||
families and all 18 profiles. Artifact tests run after server packaging and read
|
||||
the server output directory. Keep MySQL fork routing, JUnit extensions, fault-test
|
||||
environment and real-process parameters. Report completeness checks examine the
|
||||
complete relocated test tree. Checkstyle and SpotBugs still cover every production
|
||||
module. Aggregate coverage includes production classes and source, not only test
|
||||
execution data. Verification directly depends on production modules at compile
|
||||
scope for that aggregation, without becoming a published artifact.
|
||||
|
||||
Tests keep `verification` as their working directory so local `src/test/resources` paths remain valid. Surefire and Failsafe pass `managed.module.root` for shared repository fixtures and `qwen.cli.entry` for the built CLI, including profile overrides. Repository paths are resolved from the module root instead of assuming the old process working directory.
|
||||
|
||||
Use test-scoped `com.tngtech.archunit:archunit:1.5.1` under existing JUnit to enforce
|
||||
the module matrix, forbidden dependencies, package ownership and nonempty scans.
|
||||
No additional test engine is introduced. Framework/client types must not leak
|
||||
through signatures, inheritance, annotations or exception references.
|
||||
|
||||
## Implementation and acceptance
|
||||
|
||||
Implement as one change: record source/test/resource/entry-point inventories;
|
||||
extract contracts and remove cycles; move sources and define POMs; migrate
|
||||
composition, tests and delivery tooling; verify and audit the complete result.
|
||||
Maintain this design in both languages and update the development instructions.
|
||||
|
||||
Acceptance covers independent module compilation; contract purity; unchanged
|
||||
configuration and host isolation; rollback through all seven transaction entry
|
||||
points; native CSI refusal and admission; restart, takeover and lost-response
|
||||
recovery; SDK conversion and digest parity; HTTP/tenant/actor/Workspace security;
|
||||
all ordinary/executable/operator artifacts and unique nested-JAR resources; and
|
||||
unchanged CI test-family completeness.
|
||||
|
||||
Follow the module AGENTS.md shared ten-minute local Java verification budget.
|
||||
Prioritize architectural, transactional and composition regressions and affected
|
||||
real integration paths. CI retains complete Java, MySQL, Hosted, fault and MQ/OSS
|
||||
gates. Cross-language edits also require repository build, typecheck and affected
|
||||
script/meta-tests. Do not convert skipped or unavailable integrations into passes.
|
||||
Audit the full diff and evidence until two consecutive clean passes. Report local,
|
||||
CI, skipped and pending results separately. Rollback replaces application and
|
||||
distribution artifacts and does not rewrite persistent data.
|
||||
|
||||
## Implementation notes and local evidence
|
||||
|
||||
The seven-module reactor, contract extraction, composition changes, relocated
|
||||
tests/resources and delivery-script updates are implemented together. All 614
|
||||
original test source files and all 18 profile names are retained. The six original
|
||||
production resources retain their bytes and each has one production owner. New
|
||||
regressions cover compiled module boundaries, transactional default-method
|
||||
overrides, SDK failure conversion and single-instance Harness composition.
|
||||
|
||||
The Harness SDK boundary wraps both single-instance and pooled connectors. The
|
||||
single-instance factory explicitly supplies skills, extension records and the
|
||||
managed mapper to the wrapped connector; Spring cannot inject fields into a
|
||||
delegate hidden behind a returned wrapper. The settings views preserve existing
|
||||
collection references and read timing. Existing filesystem identity/admission
|
||||
checks remain at their original business/transaction positions; physical Git,
|
||||
SSH, provider and storage-identity operations cross the extracted ports.
|
||||
|
||||
Local evidence was collected on macOS with JDK 21, Maven 3.9.14, an isolated Maven
|
||||
repository, Node 26.12.1 and Bash 5 for workflow shell fixtures. It does not establish
|
||||
Linux, remote-cloud or production behavior.
|
||||
|
||||
| Area | Observed local result | Limit |
|
||||
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Production reactor | All six production modules compiled with their declared dependencies; production Checkstyle and SpotBugs passed; ordinary, executable and six operator JARs were built | These complete quality/package results precede the last composition and collection-reference fixes |
|
||||
| Architecture and transactions | Bytecode architecture check passed; all seven former default transaction entries committed and rolled back through a real Spring proxy and dedicated H2 transaction manager; 44 lock-order cases passed | H2 and proxy evidence does not establish MySQL concurrency behavior |
|
||||
| Configuration and embedding | 31 property-binding cases, non-servlet embedding and explicit internal-callback tests passed | The independent Maven embedding-host fixture remains unrun |
|
||||
| Broker and lifecycle | 199 Broker cases, 33 resident-retirement cases and 47 cancellation/command/automation cases passed | Full restart, Hosted, failure and MySQL profiles remain pending |
|
||||
| SDK, HTTP and packaging | SDK conversion, 23 API-contract cases and three packaging cases passed; inspected executable archives contain the five internal dependency JARs with no duplicate production classes/resources | The packaged artifacts predate the final audit fixes |
|
||||
| Final composition fix | The two changed production classes and the new Harness wiring regression compiled; the regression method ran successfully | This focused check was not a full Maven/Surefire run; the subsequent collection-reference-only fix still needs compilation on CI |
|
||||
| TypeScript and scripts | Repository build and typecheck passed; affected CLI tests passed 288 cases and integration-helper tests passed 22; focused ESLint and affected script/workflow rechecks passed | The broad script run initially failed under macOS Bash 3.2; Bash 5 rechecks resolved those failures, while one unchanged 600 ms timing case passed only in isolation |
|
||||
|
||||
The shared local Java ledger totals about 598 seconds. An earlier quality run
|
||||
stopped on verification-module formatting, which was corrected and rechecked.
|
||||
The configuration failures in a later selected run were corrected and all 31
|
||||
cases reran successfully. The final `mvn -DskipTests verify` attempt reached server
|
||||
SpotBugs but hit its bounded timeout; it is **not** a passing full-reactor result.
|
||||
No further Java runs were started after the final focused composition check.
|
||||
|
||||
Acceptance still requires the final source revision to pass the unfiltered Java
|
||||
reactor, aggregate coverage generation, independent embedding-host fixture and
|
||||
the existing MySQL, Hosted, fault, MQ and OSS CI gates. New Java test formatting
|
||||
and the final collection-reference fix also await that run. CI was not dispatched,
|
||||
Central publication was not attempted, and no deployment or database migration
|
||||
was performed. Rebuild the deliverables from the final revision before using or
|
||||
publishing them; local intermediate JARs are verification artifacts only.
|
||||
|
||||
## PR integration baseline
|
||||
|
||||
PR preparation incorporates `main@68f181b885` on top of the original investigation.
|
||||
The newer bounded-history queries and models remain in the repository contracts
|
||||
and persistence implementation. The upstream cleanup retry coordinator depends on
|
||||
a cleanup-debt repository contract and neutral Harness failures; its SQL scan and
|
||||
unchanged keyset/backoff behavior remain intact. The resolved-configuration entry
|
||||
point and upgrade packaging stay in server composition. All 619 current-main test
|
||||
sources plus the two embedding-fixture sources are retained, including the five
|
||||
tests added since the investigation. All six production resources are byte-identical
|
||||
to this newer main revision; the refactor adds no further protocol/schema change.
|
||||
|
||||
After integration, repository build and typecheck passed again. Reference packaging
|
||||
tests passed 25 cases with one existing skip, the five affected script suites
|
||||
passed 196 cases, CLI consumers passed 288 cases, and selected Maven/workflow checks
|
||||
passed 22 cases. The earlier Java results above predate this integration. The shared
|
||||
598-second budget was not reset: compilation, style checks and Java regressions for
|
||||
the integrated revision remain pending CI, including the new repository boundary
|
||||
and migrated upstream tests. Final acceptance requires those checks to pass.
|
||||
|
||||
## Boilerplate reduction
|
||||
|
||||
The server configuration classes use Lombok `@Getter` and `@Setter` for 349 simple
|
||||
accessors. Keep normalization, defensive copies, fallback values and the
|
||||
`EnvironmentAware` callback explicit. Internal credential-loading state remains
|
||||
excluded from generated accessors. Existing records, field defaults and
|
||||
`@DurationUnit` annotations remain unchanged; `@Data` is not used. Lombok is an
|
||||
optional provided dependency of `server`, with its version inherited from Spring
|
||||
Boot and an explicit compiler annotation processor. Exclude it from every Boot
|
||||
executable and operator archive; other production modules do not require it.
|
||||
|
||||
The SDK wrapper retains explicit interface implementations and delegates repeated
|
||||
runtime-exception conversion to two private helpers for value and void calls.
|
||||
Its size falls from 693 to 422 lines without reflection or a proxy framework.
|
||||
Context-compression submission stays deferred, and stream epoch reads, event
|
||||
reads, recovery checks and close operations still convert SDK failures after
|
||||
stream creation. Preserve result identity, arguments, call order, unrelated
|
||||
exceptions and `Error` propagation.
|
||||
|
||||
Regression coverage adds nested configuration binding, hidden internal state,
|
||||
normalization and collection semantics, immediate and deferred SDK failures, both
|
||||
stream entry points and archive exclusion. Standalone Harness composition is checked
|
||||
through session creation, resource publication and the resulting SDK request, rather
|
||||
than connector private fields. Unused test-factory overloads and private service
|
||||
forwarders are removed; existing behavioral test cases are retained.
|
||||
Source comparison confirms unchanged JavaBean signatures and SDK delegate call
|
||||
sites. These new Java regressions have not
|
||||
run locally: the shared budget remains exhausted, and earlier Java passes do not
|
||||
validate this increment. Final compilation, quality checks and regressions remain
|
||||
pending CI.
|
||||
@@ -0,0 +1,207 @@
|
||||
# Managed Agent Server Maven 多模块
|
||||
|
||||
[English](2026-10-29-managed-agent-maven-modules.md) | [简体中文](2026-10-29-managed-agent-maven-modules.zh-CN.md)
|
||||
|
||||
状态:已在工作树实现,最终验收待 CI。调研基线为 `523be857db`。本设计取代
|
||||
[源码布局](2026-10-15-managed-agent-server-layout.zh-CN.md)及
|
||||
[Java 包设计](2026-10-19-managed-agent-package-boundaries.zh-CN.md)中的单模块边界。
|
||||
|
||||
## 问题与约束
|
||||
|
||||
基线包含 391 个生产 Java 源文件、614 个测试 Java 源文件和 18 个 Maven profile。
|
||||
词法依赖分析发现分别涉及 83 和 35 个生产文件的循环。模型引用仓储,编排识别具体 JDBC
|
||||
类,服务包含 SQL,仓储调用业务服务和外部工具。配置、测试夹具、启动器和 CI 也假定
|
||||
只有一个源码目录。
|
||||
|
||||
本次通过一次完整迁移建立工程内部的编译边界。模块统一版本和发布。允许调整 Java
|
||||
源码接口,仓库内使用方同步迁移。HTTP 路由、JSON、配置键及默认值、数据库字节、事务
|
||||
边界、身份、隔离栅栏和恢复行为保持不变。不迁移数据库、不拆服务、不增加产品功能、
|
||||
不独立维护模块版本,也不附带拆解大类。产品专用集成继续放在本应用之外。
|
||||
|
||||
## 模块与依赖
|
||||
|
||||
父工程为 `com.alibaba:qwen-managed-agent-parent`,使用 `pom` packaging。生产子模块
|
||||
使用 `qwen-managed-agent-<目录名>`;`server` 保留
|
||||
`com.alibaba:qwen-managed-agent-server`。所有子模块继承父工程版本。
|
||||
|
||||
| 目录 | 职责 | 内部生产依赖 |
|
||||
| ---------------- | ----------------------------------------------------------------- | ---------------- |
|
||||
| `contracts` | 接口、值类型、异常、只读配置与纯协议规则 | 无 |
|
||||
| `control-plane` | Session、Workspace、任务、生命周期、自动化、Channel 和扩展编排 | `contracts` |
|
||||
| `runtime-broker` | Runtime 绑定、执行、租约、取消、排空及恢复 | `contracts` |
|
||||
| `persistence` | 控制面和 Broker 的 JDBC/内存仓储、schema 与事务 | `contracts` |
|
||||
| `adapters` | Harness SDK、物理供应器、HTTP 客户端、OSS、MQ、Git 和文件系统实现 | `contracts` |
|
||||
| `server` | HTTP 服务端、Spring 装配、启动、预检与运维入口 | 其他所有生产模块 |
|
||||
| `verification` | 现有测试、夹具、架构检查和产物验收;不发布 | 所有生产模块 |
|
||||
|
||||
四个实现模块不相互依赖,由 server 通过契约注入实现。一个生产 Java package 只属于
|
||||
一个产物。不通过兄弟源码目录、聚合编译 classpath、shading、通用服务注册框架或
|
||||
大而全的 common 模块绕过边界。
|
||||
|
||||
契约可以使用现有 JSON、校验注解和 Commons 库,不能引用 Spring、JDBC、Java SDK 或
|
||||
云产品客户端,也不能执行数据库、网络或进程操作。控制面可以保留 Spring 组件、调度
|
||||
和事务支持,但不能引用 JDBC 或具体外部客户端。Broker 编排继续独立于 Spring。
|
||||
|
||||
独立 Maven 产物让违规实现引用在编译阶段失败:各模块的编译 classpath 中不存在兄弟实现模块。依赖规则和 ArchUnit 检查补充这一边界;单一产物仍会把所有实现放在同一个 classpath 上。同步迁移包名让每个包只有一个产物所有者,不保留跨 JAR 的包内访问。这是已确认的内部隔离范围,继续统一版本并作为一个服务交付。
|
||||
|
||||
模块归属不按源码数量决定。多个模块共同使用、且不执行 I/O 的 Broker 请求、身份、记录和确定性协议校验归契约;运行时调度与恢复决策归 Broker 实现。因此 Broker 实现模块较小是预期结果,把共享协议规则移回实现模块反而会让使用方依赖实现。
|
||||
|
||||
## 契约与实现调整
|
||||
|
||||
将现有存储、Harness、Runtime、事件和对象存储接口及共享值类型迁入契约。只在真实
|
||||
模块边界提取接口。跨模块使用的仓储嵌套记录类型连同值语义迁移,共享模型不再依赖
|
||||
仓储实现。应用 HTTP 状态改用整数,在 HTTP 层转换,保持错误码、详情和 cause。
|
||||
保留协议字符串、JSON 名称、空值、顺序、编码和摘要输入。将纯请求身份计算和 provider
|
||||
kind 常量移出 JDBC/provider 类。
|
||||
|
||||
`ManagedAgentSettings` 只为 server 外的使用方提供只读视图。认证、内部监听、服务账号、
|
||||
可信请求头和凭据加载状态保留为 server 配置,不声明在共享契约中。`ManagedAgentProperties` 仍归 server
|
||||
并实现这些视图,保留绑定注解、时间单位、环境访问、凭据文件读取和启动检查。使用方
|
||||
接收视图或原有标量,不引入配置快照或动态刷新。
|
||||
|
||||
在 JDBC 实现中显式覆盖 `AgentStateStore` 的七个默认事务入口,保持原事务语义。
|
||||
移除接口注解不能丢失委托重载入口的代理拦截。现有 Spring 与原生 JDBC 事务机制分别
|
||||
保留。同连接操作和包内锁辅助方法留在 persistence,普通仓储契约不暴露 `Connection`。
|
||||
每个原事务体整体迁移,不拆成分别提交的 CRUD 操作。
|
||||
|
||||
`NativeCsiExecutionRepository` 承接现有私有 CSI prepare 和 batch read 能力。原 JDBC
|
||||
binding 仓储实现它,内存仓储不实现。Broker 使用能力接口而非具体 JDBC 类型。能力
|
||||
发现不替代事务 admission、原 DataSource 身份、writer/generation 校验及锁。缺失能力
|
||||
保持原拒绝行为。中立存储异常保留真实存储故障分类,不暴露 JDBC 异常类型。Broker
|
||||
操作接口只包含实现模块外的实际消费操作;没有此类使用方的便利重载仅保留在实现类中。
|
||||
|
||||
恢复查询和 SQL 证据读取进入 persistence。Channel 服务注入现有仓储接口,不自行
|
||||
创建 JDBC 仓储。分离扩展事务、归档访问和业务协调,保持原顺序。提取用户消息授权及
|
||||
输入策略,不将授权移出原事务,也不引入策略对消息仓储的依赖。Git 和 SSH 在原调用
|
||||
位置通过明确的接口访问。
|
||||
|
||||
仅 adapters 直接依赖 Java SDK 和云产品客户端。将 Harness 结果以及同步、异步和回调
|
||||
异常映射为契约值。保留 HTTP、传输、协议、generation、能力不匹配和拒绝的区别,包含
|
||||
重试所需输入、状态、原因和 cause。SDK payload digest 与 permission rules 算法通过
|
||||
小接口委托复用,不复制实现。Workspace 范围和授权 wrapper 留在控制面,物理供应及
|
||||
通信进入适配器,实例创建和监听器归 server 装配。
|
||||
|
||||
CSI 维护保留持久化顺序:锁定并保存原操作尝试,执行限定的 Kubernetes 操作,然后
|
||||
通过栅栏校验保存回执或未知结果。保留 UID/resourceVersion、请求字节及回执摘要。
|
||||
响应丢失保持 UNKNOWN,不允许盲目重放。运维入口保留独立应用上下文,不启动公共服务
|
||||
或业务调度器。
|
||||
|
||||
## 构建、资源与验证
|
||||
|
||||
保留 JDK 21、Spring Boot 3.5.16 和既有构建插件版本。父工程管理版本,各子模块声明
|
||||
自身依赖。reactor 构建全部模块,仅 `server` 执行 Boot repackage。保留可执行及运维
|
||||
classifier 和服务发布坐标,产物移至 `server/target`。SDK 保留现有单独安装的构建前提。
|
||||
父 POM 与全部生产依赖同版本发布,verification 不发布。
|
||||
|
||||
server 装配保留无 Servlet 嵌入、显式内部回调和独立服务三种方式。专用数据源、事务
|
||||
管理器、mapper、执行器和调度器保留名称,与宿主默认 Bean 隔离。限定组件扫描,并让
|
||||
每项资源只有一个关闭责任方。
|
||||
|
||||
| 资源 | 归属 |
|
||||
| ------------------------- | -------------- |
|
||||
| OpenAPI 和共享协议 schema | `contracts` |
|
||||
| CSI 原生工具协议声明 | `contracts` |
|
||||
| V1 数据库 schema | `persistence` |
|
||||
| 应用 YAML 与 Spring 注册 | `server` |
|
||||
| 测试夹具与 JUnit 服务注册 | `verification` |
|
||||
|
||||
classpath 资源名保持不变,每份资源只有一个产物所有者。TypeScript 工具结果 schema
|
||||
仍为权威来源。同步更新启动器、Docker、分发和版本检查、WebShell 契约读取、历史路径
|
||||
回退及 CI 使用方。资源移动不增加协议/schema 版本。
|
||||
|
||||
将全部现有测试和夹具迁至 verification,保留执行族和全部 18 个 profile。产物测试
|
||||
在 server 打包之后读取其输出目录。保留 MySQL fork 路由、JUnit 扩展、故障测试环境及
|
||||
真实进程参数。报告完整性检查核对完整的新测试树。Checkstyle 与 SpotBugs 继续覆盖
|
||||
每个生产模块。聚合覆盖率包含生产类和源码,而不只包含测试执行数据。verification
|
||||
为此以 compile scope 直接依赖生产模块,自身仍不发布。
|
||||
|
||||
测试继续以 `verification` 为工作目录,使本地 `src/test/resources` 路径保持有效。Surefire 和 Failsafe 传入用于共享仓库夹具的 `managed.module.root` 以及指向已构建 CLI 的 `qwen.cli.entry`,包括覆盖插件配置的 profile。仓库路径从模块根目录解析,不再假定旧进程工作目录。
|
||||
|
||||
使用测试范围的 `com.tngtech.archunit:archunit:1.5.1`,在现有 JUnit 下验证模块矩阵、
|
||||
禁止依赖、包归属及非空扫描,不增加测试引擎。框架/客户端类型不得从签名、继承、注解
|
||||
或异常引用中泄漏。
|
||||
|
||||
## 实施与验收
|
||||
|
||||
一次整体交付:记录源码、测试、资源和入口清单;提取契约、解除循环;移动源码并建立
|
||||
POM;迁移装配、测试和交付工具;验证并自审完整结果。同步维护双语设计与开发说明。
|
||||
|
||||
验收覆盖独立模块编译、契约纯度、配置及宿主隔离、七个事务入口回滚、原生 CSI 拒绝和
|
||||
admission、重启/接管/响应丢失恢复、SDK 转换和摘要一致性、HTTP/tenant/actor/Workspace
|
||||
安全、全部普通/可执行/运维产物与唯一嵌套 JAR 资源,以及原 CI 测试族完整性。
|
||||
|
||||
本地 Java 验证遵守模块 AGENTS.md 共享十分钟预算,优先架构、事务和装配回归及受影响
|
||||
的真实集成路径。完整 Java、MySQL、Hosted、故障和 MQ/OSS 门禁由 CI 承担。跨语言
|
||||
修改还需仓库 build、typecheck 及相关脚本/meta-test。跳过或不可用的集成不能记为通过。
|
||||
完整 diff 和证据需连续两遍干净自审。分别报告本地、CI、跳过和待验证结果。回退替换
|
||||
应用及分发产物,不重写持久化数据。
|
||||
|
||||
## 实现说明与本地证据
|
||||
|
||||
七模块 reactor、契约提取、装配调整、测试/资源迁移及交付脚本更新作为同一批次实现。
|
||||
原有 614 个测试源文件和 18 个 profile 名称全部保留。六份原有生产资源保持字节不变,
|
||||
各有唯一生产模块归属。新增回归覆盖编译后模块边界、默认事务方法的实现覆盖、SDK
|
||||
异常转换及单实例 Harness 装配。
|
||||
|
||||
Harness SDK 边界同时包装单实例和池化 connector。单实例工厂显式向被包装的 connector
|
||||
提供 skills、扩展记录和专用 mapper;Spring 无法向返回 wrapper 后隐藏的 delegate
|
||||
注入字段。配置视图保留原有集合引用和读取时机。现有文件系统身份/admission 检查留在
|
||||
原业务/事务位置;物理 Git、SSH、provider 和存储身份操作通过提取的接口调用。
|
||||
|
||||
本地证据来自 macOS、JDK 21、Maven 3.9.14、隔离的 Maven 仓库、Node 26.12.1,以及
|
||||
用于工作流 Shell 夹具的 Bash 5,不能证明 Linux、远端云环境或生产行为。
|
||||
|
||||
| 范围 | 本地观测结果 | 限制 |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| 生产 reactor | 六个生产模块均按声明依赖编译;生产 Checkstyle 与 SpotBugs 通过;普通、可执行及六个运维 JAR 已构建 | 这组完整质量/打包结果早于最后的装配与集合引用修正 |
|
||||
| 架构与事务 | 字节码架构检查通过;七个原默认事务入口经真实 Spring 代理和专用 H2 事务管理器完成提交及回滚;44 个锁顺序用例通过 | H2 与代理证据不能证明 MySQL 并发行为 |
|
||||
| 配置与嵌入 | 31 个属性绑定用例、无 Servlet 嵌入及显式内部回调测试通过 | 独立 Maven embedding-host 夹具尚未运行 |
|
||||
| Broker 与生命周期 | 199 个 Broker、33 个驻留 Workspace 退休,以及 47 个取消/命令/自动化用例通过 | 完整重启、Hosted、故障及 MySQL profile 待验收 |
|
||||
| SDK、HTTP 与打包 | SDK 转换、23 个 API 契约及三个打包用例通过;检查过的可执行包包含五个内部依赖 JAR,无重复生产类/资源 | 这些产物早于最终自审修正 |
|
||||
| 最后装配修正 | 两个改动的生产类和新增 Harness 装配回归完成编译,回归方法执行成功 | 该定向检查不是完整 Maven/Surefire 执行;随后仅涉及集合引用的修正仍需 CI 编译 |
|
||||
| TypeScript 与脚本 | 仓库 build 和 typecheck 通过;受影响 CLI 测试 288 项、集成辅助测试 22 项通过;定向 ESLint 及相关脚本/工作流复查通过 | 首次较广脚本执行因 macOS Bash 3.2 失败,Bash 5 复查解决了这些失败;其中一个未修改的 600 ms 时序用例仅在隔离执行时通过 |
|
||||
|
||||
共享本地 Java 账本累计约 598 秒。较早的质量检查在验证模块格式处停止,修正后已复查。
|
||||
后续定向执行中的配置失败已修正,31 个用例全部重跑通过。最后一次
|
||||
`mvn -DskipTests verify` 到达 server SpotBugs 后触发限定超时,**不能记为完整 reactor
|
||||
通过**。最后的定向装配检查后未再启动 Java 验证。
|
||||
|
||||
验收仍要求最终源码版本通过未过滤的 Java reactor、聚合覆盖率生成、独立
|
||||
embedding-host 夹具及现有 MySQL、Hosted、故障、MQ 和 OSS CI 门禁。新增 Java 测试
|
||||
格式和最后的集合引用修正也等待该次执行。尚未派发 CI、尝试 Central 发布、部署服务或
|
||||
迁移数据库。使用或发布前须从最终版本重新构建交付物;本地中间 JAR 仅是验证产物。
|
||||
|
||||
## PR 整合基线
|
||||
|
||||
准备 PR 时,在原调研基础上接入了 `main@68f181b885`。较新的有界历史查询及模型保留在
|
||||
仓储契约和持久化实现中。主干新增的清理重试协调器依赖清理债务仓储契约及中立 Harness
|
||||
异常;SQL 扫描和原有 keyset/退避行为保持不变。配置解析入口和升级打包留在 server
|
||||
装配层。当前主干的 619 个测试源文件及两个嵌入夹具源文件全部保留,包括调研后新增的
|
||||
五个测试。六份生产资源与这一较新的主干版本字节一致,重构不再附加协议/schema 变更。
|
||||
|
||||
整合后,仓库 build 和 typecheck 再次通过。参考包测试通过 25 项、保留一个既有跳过项,
|
||||
五组相关脚本测试通过 196 项、CLI 使用方测试通过 288 项、定向 Maven/工作流检查通过
|
||||
22 项。上文的 Java 证据早于本次整合。共享 598 秒预算没有重置:
|
||||
整合版本的编译、格式检查及 Java 回归仍待 CI,包括新增仓储边界和迁入的主干测试。
|
||||
最终验收要求这些检查通过。
|
||||
|
||||
## 样板代码精简
|
||||
|
||||
server 配置类使用 Lombok `@Getter` 和 `@Setter` 替代 349 个简单访问方法。
|
||||
规范化、防御性复制、默认值回退及 `EnvironmentAware` 回调保持显式实现。凭据加载
|
||||
内部状态排除在生成访问方法之外。现有 record、字段默认值和 `@DurationUnit` 注解
|
||||
不变,不使用 `@Data`。Lombok 仅作为 `server` 的 optional provided 依赖,版本继承
|
||||
Spring Boot,并显式配置编译器注解处理器。全部 Boot 可执行包和运维包排除 Lombok;
|
||||
其他生产模块无需依赖它。
|
||||
|
||||
SDK 包装保留显式接口实现,将重复的运行时异常转换集中到两个私有辅助方法,分别处理
|
||||
有返回值和无返回值的调用。文件从 693 行缩减至 422 行,不引入反射或代理框架。
|
||||
上下文压缩提交仍延迟执行;流创建后的 epoch 读取、事件读取、恢复检查及关闭操作
|
||||
继续转换 SDK 异常。保留返回对象身份、参数、调用顺序、无关异常和 `Error` 传播。
|
||||
|
||||
回归覆盖新增嵌套配置绑定、内部状态不可绑定、规范化与集合语义、同步和延迟 SDK
|
||||
异常、两个流入口及产物排除。单实例 Harness 装配通过创建会话、资源发布和最终 SDK
|
||||
请求检查,不再检查连接器私有字段。删除未使用的测试工厂重载和业务服务私有转发,
|
||||
保留现有行为测试用例。源码比对确认 JavaBean 方法签名及 SDK 委托调用点保持不变。
|
||||
这些新增 Java 回归尚未在本地运行:共享预算已用尽,较早的 Java 通过记录不能证明
|
||||
本次增量通过。最终编译、质量检查及回归仍待 CI。
|
||||
@@ -174,7 +174,7 @@ mvn -f packages/sdk-java/qwencode/pom.xml -DskipTests -Dgpg.skip=true install
|
||||
mvn -f packages/managed-agent-server/pom.xml verify
|
||||
npm run package:managed-runtime -- \
|
||||
--target darwin-arm64 \
|
||||
--server-jar packages/managed-agent-server/target/qwen-managed-agent-server-0.1.0-alpha-exec.jar \
|
||||
--server-jar packages/managed-agent-server/server/target/qwen-managed-agent-server-0.1.0-alpha-exec.jar \
|
||||
--jre-home /verified/java-21-home \
|
||||
--out-dir /output/managed-runtime
|
||||
```
|
||||
@@ -182,7 +182,7 @@ npm run package:managed-runtime -- \
|
||||
The runtime builder only needs the matching server JAR, Java runtime and CLI
|
||||
version from this checkout. It does not require built CLI assets or a Node
|
||||
archive. By default it writes to
|
||||
`packages/managed-agent-server/target/managed-runtime`, outside the CLI's
|
||||
`packages/managed-agent-server/server/target/managed-runtime`, outside the CLI's
|
||||
`dist/` assets. It emits the runtime archive and a per-archive `.sha256` file. The
|
||||
normal CLI is built/released separately.
|
||||
|
||||
@@ -192,7 +192,7 @@ instead of `--jre-home`:
|
||||
```sh
|
||||
npm run package:managed-runtime -- \
|
||||
--target darwin-arm64 \
|
||||
--server-jar packages/managed-agent-server/target/qwen-managed-agent-server-0.1.0-alpha-exec.jar \
|
||||
--server-jar packages/managed-agent-server/server/target/qwen-managed-agent-server-0.1.0-alpha-exec.jar \
|
||||
--without-jre \
|
||||
--out-dir /output/managed-runtime
|
||||
```
|
||||
@@ -204,7 +204,7 @@ The lightweight builder needs no Java input. For an optional complete distributi
|
||||
npm run package:standalone -- \
|
||||
--target darwin-arm64 \
|
||||
--node-archive /verified/node-v22-darwin-arm64.tar.gz \
|
||||
--managed-server-jar packages/managed-agent-server/target/qwen-managed-agent-server-0.1.0-alpha-exec.jar \
|
||||
--managed-server-jar packages/managed-agent-server/server/target/qwen-managed-agent-server-0.1.0-alpha-exec.jar \
|
||||
--managed-jre-home /verified/java-21-home \
|
||||
--out-dir /output/managed-local
|
||||
```
|
||||
|
||||
@@ -147,14 +147,14 @@ mvn -f packages/sdk-java/qwencode/pom.xml -DskipTests -Dgpg.skip=true install
|
||||
mvn -f packages/managed-agent-server/pom.xml verify
|
||||
npm run package:managed-runtime -- \
|
||||
--target darwin-arm64 \
|
||||
--server-jar packages/managed-agent-server/target/qwen-managed-agent-server-0.1.0-alpha-exec.jar \
|
||||
--server-jar packages/managed-agent-server/server/target/qwen-managed-agent-server-0.1.0-alpha-exec.jar \
|
||||
--jre-home /verified/java-21-home \
|
||||
--out-dir /output/managed-runtime
|
||||
```
|
||||
|
||||
runtime 构建器只要求匹配的服务 JAR、Java runtime 和本 checkout 中的 CLI 版本,
|
||||
不依赖编译好的 CLI 资源或 Node archive。默认输出到
|
||||
`packages/managed-agent-server/target/managed-runtime`,位于 CLI 的 `dist/` 资源树
|
||||
`packages/managed-agent-server/server/target/managed-runtime`,位于 CLI 的 `dist/` 资源树
|
||||
之外。输出 runtime archive 及单独的 `.sha256`
|
||||
文件。普通 CLI 独立构建和发行。
|
||||
|
||||
@@ -163,7 +163,7 @@ runtime 构建器只要求匹配的服务 JAR、Java runtime 和本 checkout 中
|
||||
```sh
|
||||
npm run package:managed-runtime -- \
|
||||
--target darwin-arm64 \
|
||||
--server-jar packages/managed-agent-server/target/qwen-managed-agent-server-0.1.0-alpha-exec.jar \
|
||||
--server-jar packages/managed-agent-server/server/target/qwen-managed-agent-server-0.1.0-alpha-exec.jar \
|
||||
--without-jre \
|
||||
--out-dir /output/managed-runtime
|
||||
```
|
||||
@@ -175,7 +175,7 @@ npm run package:managed-runtime -- \
|
||||
npm run package:standalone -- \
|
||||
--target darwin-arm64 \
|
||||
--node-archive /verified/node-v22-darwin-arm64.tar.gz \
|
||||
--managed-server-jar packages/managed-agent-server/target/qwen-managed-agent-server-0.1.0-alpha-exec.jar \
|
||||
--managed-server-jar packages/managed-agent-server/server/target/qwen-managed-agent-server-0.1.0-alpha-exec.jar \
|
||||
--managed-jre-home /verified/java-21-home \
|
||||
--out-dir /output/managed-local
|
||||
```
|
||||
|
||||
+2
-1
@@ -78,6 +78,7 @@ export default tseslint.config(
|
||||
'packages/desktop/runtime/**',
|
||||
'packages/core/src/skills/bundled/browser-use/runtime/**',
|
||||
'packages/desktop/src-tauri/target/**',
|
||||
'packages/managed-agent-server/**/target/**',
|
||||
'packages/live-host/**', // standalone Electron app with its own Node test conventions
|
||||
'packages/cua-driver/**', // vendored trycua/cua driver (Rust + scripts); not qwen-code TS
|
||||
'packages/mobile-mcp/**', // vendored mobile-next/mobile-mcp; has own eslint config
|
||||
@@ -506,7 +507,7 @@ export default tseslint.config(
|
||||
'packages/*/scripts/**/*.js',
|
||||
'packages/*/scripts/**/*.mjs',
|
||||
'packages/*/build.mjs',
|
||||
'packages/managed-agent-server/src/test/resources/*.mjs',
|
||||
'packages/managed-agent-server/verification/src/test/resources/**/*.mjs',
|
||||
// web-templates' export-html template build scripts also run with `node`.
|
||||
'packages/*/src/export-html/*.mjs',
|
||||
// Verification reproducer scripts under docs/ also run with `node`.
|
||||
|
||||
@@ -45,7 +45,7 @@ const baselinePath = path.resolve(
|
||||
'integration-tests/baselines/hosted-latency.json',
|
||||
);
|
||||
const reportPath = path.resolve(
|
||||
'packages/managed-agent-server/target/hosted-latency-baseline.json',
|
||||
'packages/managed-agent-server/verification/target/hosted-latency-baseline.json',
|
||||
);
|
||||
const samples: HostedLatencySample[] = [];
|
||||
let sample: HostedLatencySample;
|
||||
@@ -366,7 +366,7 @@ try {
|
||||
'integration-tests/helpers/hosted-harness-process.ts',
|
||||
'integration-tests/helpers/hosted-relay-headers.ts',
|
||||
'integration-tests/fake-openai-server.ts',
|
||||
'packages/managed-agent-server/src/test/java/com/alibaba/qwen/code/managedagent/HostedWorkspaceToolTurnIT.java',
|
||||
'packages/managed-agent-server/verification/src/test/java/com/alibaba/qwen/code/managedagent/HostedWorkspaceToolTurnIT.java',
|
||||
];
|
||||
const sourceHashes = Object.fromEntries(
|
||||
await Promise.all(
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
const REPO = resolve(import.meta.dirname, '../../../..');
|
||||
const MANAGED_SURFACE_FILE = resolve(
|
||||
REPO,
|
||||
'packages/managed-agent-server/src/test/resources/capability-parity/managed-surface.txt',
|
||||
'packages/managed-agent-server/verification/src/test/resources/capability-parity/managed-surface.txt',
|
||||
);
|
||||
const ROUTE_METHODS: ReadonlySet<string> = new Set([
|
||||
'GET',
|
||||
|
||||
@@ -551,7 +551,7 @@ it('fixes full Parts, original IDs, exact input bytes and final definitions befo
|
||||
const pinned = JSON.parse(
|
||||
await readFile(
|
||||
new URL(
|
||||
'../../../managed-agent-server/src/main/resources/csi-native-file-declarations.json',
|
||||
'../../../managed-agent-server/contracts/src/main/resources/csi-native-file-declarations.json',
|
||||
import.meta.url,
|
||||
),
|
||||
'utf8',
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('Hosted immutable attachment input', () => {
|
||||
const spec = JSON.parse(
|
||||
readFileSync(
|
||||
new URL(
|
||||
'../../../managed-agent-server/src/main/resources/openapi/managed-agent-public-api.openapi.json',
|
||||
'../../../managed-agent-server/contracts/src/main/resources/openapi/managed-agent-public-api.openapi.json',
|
||||
import.meta.url,
|
||||
),
|
||||
'utf8',
|
||||
|
||||
@@ -1 +1 @@
|
||||
/target/
|
||||
**/target/
|
||||
@@ -86,7 +86,14 @@ cross-language changes.
|
||||
|
||||
### Maven and MySQL checks
|
||||
|
||||
Use JDK 21 and Maven 3.8.9 or newer; run Maven from this directory.
|
||||
Use JDK 21 and Maven 3.8.9 or newer; run Maven from this directory. This is the
|
||||
reactor parent. Production modules compile independently; all tests and fixtures
|
||||
live in `verification`. Preserve the dependency matrix in the
|
||||
[Maven modules design](../../docs/design/2026-10-29-managed-agent-maven-modules.md).
|
||||
Checkstyle and SpotBugs apply to every production module. Test reports and
|
||||
aggregate coverage live under `verification/target`; executable artifacts live
|
||||
under `server/target`. The verification module must never be installed or
|
||||
published.
|
||||
|
||||
Before testing, check the toolchain and that the installed sibling SDK matches
|
||||
the source being tested. Reuse a valid build; otherwise install it once into
|
||||
@@ -144,11 +151,9 @@ work follows this module's budget; CI retains the full cross-language gates.
|
||||
Documentation-only changes need formatting and reference checks and do not
|
||||
invalidate unchanged Java test results.
|
||||
|
||||
Maven report output under `target/` (javadoc `reports/apidocs`, JaCoCo
|
||||
`site/jacoco`) contains generated JavaScript that the repository-root ESLint
|
||||
sweep does not ignore, so a root `npm run lint` fails while those directories
|
||||
exist. Run `mvn clean` or delete the `target/` directories before running root
|
||||
checks in the same checkout.
|
||||
Maven output under every module's `target/` is ignored by Git, ESLint and
|
||||
Prettier. Keep these exclusions when adding modules so generated reports and
|
||||
packaged fixtures are not linted or committed as source.
|
||||
|
||||
## Code Review
|
||||
|
||||
@@ -160,7 +165,7 @@ checks in the same checkout.
|
||||
idempotency and writer lease/generation checks across restarts: a stale owner
|
||||
must not commit durable state or release a newer owner's resources.
|
||||
- Server and Broker use the
|
||||
[shared schema baseline](src/main/resources/db/managed-schema/V1__managed_schema.sql).
|
||||
[shared schema baseline](persistence/src/main/resources/db/managed-schema/V1__managed_schema.sql).
|
||||
Until the first formal Managed Agent release, edit this V1 baseline in place;
|
||||
do not add V2+ migrations or increment the database schema version. Recreate
|
||||
development catalogs after baseline changes rather than maintaining upgrades.
|
||||
@@ -169,7 +174,7 @@ checks in the same checkout.
|
||||
migrations or use Flyway repair/history edits to bypass that refusal. Follow
|
||||
the README's current schema policy rather than assuming an upgrade path.
|
||||
- Public and WebShell API changes must keep the
|
||||
[OpenAPI contract](src/main/resources/openapi/managed-agent-public-api.openapi.json),
|
||||
[OpenAPI contract](contracts/src/main/resources/openapi/managed-agent-public-api.openapi.json),
|
||||
mapped routes, `SurfaceRegistry`, and contract tests consistent. Run
|
||||
`ManagedAgentApiContractTest` and `SurfaceRegistryGateTest` for those changes.
|
||||
Regenerate WebShell types from the repository root with
|
||||
|
||||
@@ -17,7 +17,7 @@ RUN mvn --batch-mode --no-transfer-progress \
|
||||
# exists. sh has no nullglob, so a zero-match glob yields the pattern
|
||||
# itself — break on it so the count check below still fails, and name
|
||||
# the expectation loudly instead of dying at cp.
|
||||
&& cd packages/managed-agent-server/target \
|
||||
&& cd packages/managed-agent-server/server/target \
|
||||
&& main="" && count=0 \
|
||||
&& for f in qwen-managed-agent-server-*-exec.jar; do \
|
||||
[ -e "$f" ] || break; \
|
||||
|
||||
@@ -17,7 +17,7 @@ alive. The Broker reconciles and adopts bindings through recovery-capable
|
||||
provisioners. The embedding service owns those provisioners, reprovisioning
|
||||
policy, and Session rebind policy.
|
||||
|
||||
Broker and Server sources, resources and tests share this Maven module. Keep their Java packages separate. The server depends on the Java SDK at `../sdk-java/qwencode`; install that SDK before building.
|
||||
The parent builds six production Maven modules: `contracts`, `control-plane`, `runtime-broker`, `persistence`, `adapters`, and `server`. Tests live in `verification`. Keep each production Java package in one module. Only `adapters` depends on the Java SDK at `../sdk-java/qwencode`; install that SDK before building. Follow [AGENTS.md](AGENTS.md) for focused verification and the shared local budget.
|
||||
|
||||
Use JDK 21 or later to build and run this module.
|
||||
|
||||
@@ -25,7 +25,7 @@ Build and test with:
|
||||
|
||||
```bash
|
||||
mvn test
|
||||
mvn checkstyle:check checkstyle:check@runtime-broker-checkstyle
|
||||
mvn checkstyle:check
|
||||
mvn verify
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Qwen Managed Agent Server
|
||||
|
||||
The application lives in `packages/managed-agent-server`, alongside the Java SDK. Runtime Broker sources, resources and tests are part of this Maven module; see [Runtime Broker internals](docs/runtime-broker.md). Install the Java SDK before building this application; no separate Broker artifact is needed.
|
||||
The application lives in `packages/managed-agent-server`, alongside the Java SDK. It builds as one Maven reactor with six production modules and a verification module. The executable remains one Spring Boot service. Install the Java SDK before building the reactor; see [Runtime Broker internals](docs/runtime-broker.md).
|
||||
|
||||
Standalone Spring Boot control plane for the Qwen Code Hosted Harness. It has
|
||||
no DataWorks dependency. The default loopback listen address keeps the
|
||||
@@ -91,20 +91,32 @@ adoption only. Linux operators may explicitly enable
|
||||
trusted-host contract. Missing or conflicting physical identity markers
|
||||
remain closed to execution; startup does not re-register or repair them.
|
||||
|
||||
## Java package layout
|
||||
## Maven modules and Java packages
|
||||
|
||||
Business components live in `managedagent.session`, `workspace`, `artifact`,
|
||||
`channel`, `automation`, `catalog` and `event`. Controllers and HTTP filters
|
||||
remain in `managedagent.api`; shared contracts and application errors live in
|
||||
`managedagent.model` and `exception`. The shared SQL aggregate remains in
|
||||
`managedagent.persistence` so its transaction and locking helpers stay
|
||||
package-private. Broker providers, HTTP adapters and credential protection live
|
||||
in `runtimebroker.provisioning`, `transport` and `security` respectively. The
|
||||
separate `maintenance` configuration remains outside the server's component scan.
|
||||
| Module | Responsibility | Internal dependencies |
|
||||
| ---------------- | --------------------------------------------------------------------------------------- | -------------------------- |
|
||||
| `contracts` | Service and repository interfaces, shared values, settings views and protocol rules | None |
|
||||
| `control-plane` | Session, Workspace, catalog, lifecycle, Channel, automation and extension orchestration | `contracts` |
|
||||
| `runtime-broker` | Runtime execution, binding, leases, cancellation and recovery coordination | `contracts` |
|
||||
| `persistence` | JDBC/memory repositories, schema and atomic database operations | `contracts` |
|
||||
| `adapters` | Harness SDK, Runtime providers, HTTP clients, OSS, MQ, Git and filesystem operations | `contracts` |
|
||||
| `server` | HTTP endpoints, Spring assembly, startup and maintenance entry points | All five modules |
|
||||
| `verification` | Unit, integration, architecture and packaging tests; not published | All six production modules |
|
||||
|
||||
These are packages in one Maven module. See the package-boundary design:
|
||||
[English](../../docs/design/2026-10-19-managed-agent-package-boundaries.md) |
|
||||
[简体中文](../../docs/design/2026-10-19-managed-agent-package-boundaries.zh-CN.md).
|
||||
The parent coordinate is `com.alibaba:qwen-managed-agent-parent`. All modules share its version; the server keeps `com.alibaba:qwen-managed-agent-server`. Each production Java package belongs to one module. Keep implementation modules independent of one another: their collaboration uses contracts injected by server configuration. SQL helpers sharing a connection remain in `managedagent.persistence` or `runtimebroker.persistence`. The Broker service lives in `runtimebroker.service`; physical clients and providers live in the adapter module. Maintenance applications retain their separate component scan.
|
||||
|
||||
Only `server` creates executable JARs. Its ordinary library JAR and the existing `exec` and maintenance classifiers are under `server/target/`. Dependencies are ordinary JARs nested under `BOOT-INF/lib` in executable artifacts. Install the reactor before using the server POM directly or embedding the library in another Maven project:
|
||||
|
||||
```bash
|
||||
mvn -pl server -am -DskipTests install
|
||||
mvn -f server/pom.xml spring-boot:run
|
||||
```
|
||||
|
||||
Embedding imports `ManagedAgentConfiguration`; internal callbacks additionally import `ManagedAgentInternalConfiguration`. The standalone application also enables the public web configuration. Add the ordinary server artifact as the dependency; Maven resolves the other five modules transitively. The verification module is never installed or deployed. The standalone application YAML is excluded from the ordinary library JAR.
|
||||
|
||||
Server configuration accessors use Lombok through the server POM's annotation processor; enable annotation processing when compiling through an IDE. Keep accessors with normalization, copying or fallback logic explicit. Lombok is excluded from executable and maintenance JARs and is not a runtime dependency of embedding applications.
|
||||
|
||||
See the complete Maven boundary design: [English](../../docs/design/2026-10-29-managed-agent-maven-modules.md) | [简体中文](../../docs/design/2026-10-29-managed-agent-maven-modules.zh-CN.md).
|
||||
|
||||
## Integration status
|
||||
|
||||
@@ -119,10 +131,10 @@ results below do not establish completion of later slices.
|
||||
|
||||
## API contract
|
||||
|
||||
`src/main/resources/openapi/managed-agent-public-api.openapi.json` is the
|
||||
`contracts/src/main/resources/openapi/managed-agent-public-api.openapi.json` is the
|
||||
single source for the public and WebShell routes. `ManagedAgentApiContractTest`
|
||||
compares the mapped routes, the `ApiModels` records and real responses with it;
|
||||
`src/test/resources/openapi/contract-known-gaps.txt` lists the differences that
|
||||
`verification/src/test/resources/openapi/contract-known-gaps.txt` lists the differences that
|
||||
a later slice still has to close; none remain after D4. The WebShell client types are generated from the
|
||||
same file by `npm run generate:managed-agent-api` in `packages/web-shell`.
|
||||
The test-tree `SurfaceRegistry` names every mounted route, internal ones
|
||||
@@ -335,7 +347,8 @@ export QWEN_MANAGED_AGENT_HARNESS_TOKEN='replace-me'
|
||||
export QWEN_MANAGED_AGENT_CAPABILITY_DIGEST='sha256:replace-with-64-hex-characters'
|
||||
export QWEN_MANAGED_AGENT_WORKSPACE_ID='local-dev-workspace'
|
||||
|
||||
mvn spring-boot:run
|
||||
mvn -pl server -am -DskipTests install
|
||||
mvn -f server/pom.xml spring-boot:run
|
||||
```
|
||||
|
||||
Instead of the token and digest, `QWEN_MANAGED_AGENT_HARNESS_CREDENTIALS_FILE`
|
||||
@@ -719,7 +732,8 @@ source <printed spring.env path>
|
||||
export SPRING_DATASOURCE_URL='jdbc:mysql://127.0.0.1:3306/qwen_managed_agent'
|
||||
export SPRING_DATASOURCE_USERNAME='qwen'
|
||||
export SPRING_DATASOURCE_PASSWORD='replace-me'
|
||||
mvn -f packages/managed-agent-server/pom.xml spring-boot:run
|
||||
mvn -f packages/managed-agent-server/pom.xml -pl server -am -DskipTests install
|
||||
mvn -f packages/managed-agent-server/server/pom.xml spring-boot:run
|
||||
```
|
||||
|
||||
The daemon, Harness and Java URLs print at startup with the `spring.env`
|
||||
@@ -1268,7 +1282,7 @@ Sessions. Persisted bound Sessions use the private Workspace execution path
|
||||
below.
|
||||
|
||||
Managed Server and Runtime Broker share the single 38-table baseline in
|
||||
[`src/main/resources/db/managed-schema/V1__managed_schema.sql`](src/main/resources/db/managed-schema/V1__managed_schema.sql).
|
||||
[`persistence/src/main/resources/db/managed-schema/V1__managed_schema.sql`](persistence/src/main/resources/db/managed-schema/V1__managed_schema.sql).
|
||||
Until the first formal Managed Agent release, schema changes update this V1 baseline
|
||||
in place. Do not add V2+ migrations or increment the database schema version before
|
||||
that release. Recreate development catalogs when the baseline changes; pre-release
|
||||
@@ -1431,8 +1445,9 @@ crash. Database credentials come from `W1_JDBC_URL`, `W1_JDBC_USER` and
|
||||
`W1_JDBC_PASSWORD` environment variables:
|
||||
|
||||
```bash
|
||||
mvn -q -DskipTests compile exec:java \
|
||||
-Dexec.mainClass=com.alibaba.qwen.code.managedagent.persistence.WorkspaceStorageRegistrationMain \
|
||||
mvn -q -pl server -am -DskipTests -Dgpg.skip=true install
|
||||
mvn -q -pl server exec:java \
|
||||
-Dexec.mainClass=com.alibaba.qwen.code.managedagent.maintenance.WorkspaceStorageRegistrationMain \
|
||||
-Dexec.args='register tenant-a storage-a /absolute/canonical/workspace-a <operation-uuid> --offline-confirmed'
|
||||
```
|
||||
|
||||
@@ -1443,7 +1458,7 @@ wraps. The same entry is available from a shipped Spring Boot fat jar, without t
|
||||
|
||||
```bash
|
||||
java -cp /path/to/app.jar \
|
||||
-Dloader.main=com.alibaba.qwen.code.managedagent.persistence.WorkspaceStorageRegistrationMain \
|
||||
-Dloader.main=com.alibaba.qwen.code.managedagent.maintenance.WorkspaceStorageRegistrationMain \
|
||||
org.springframework.boot.loader.launch.PropertiesLauncher \
|
||||
register tenant-a storage-a /absolute/canonical/workspace-a '<operation-uuid>' --offline-confirmed
|
||||
```
|
||||
@@ -1542,7 +1557,7 @@ networks whose peers you trust.
|
||||
|
||||
## Managed Session Store verification
|
||||
|
||||
Unit and H2 contract tests run with the normal Maven test phase. `mvn verify`
|
||||
Run Maven from the parent directory. Unit and H2 tests run in `verification` with the normal test phase; all original profile names and test families remain available from the parent reactor. Focused commands such as `mvn -Dtest=ModuleArchitectureTest,ManagedAgentEmbeddingTest test` build required production modules first. Test reports are under `verification/target/{surefire-reports,failsafe-reports}` and aggregate coverage under `verification/target/site/jacoco-aggregate`. Do not run the verification POM alone against stale installed same-version artifacts. `mvn clean verify` checks the compiled module boundaries, the existing test families and packaged server artifacts. The embedding-host fixture is under `verification/src/test/embedding-host`; after installing the production reactor, run its normal and `embedded` profiles to verify an independent host. `mvn verify`
|
||||
additionally runs the SpotBugs high-confidence gate (Maven 3.8.9+): a new
|
||||
warning fails the build, and a false positive goes into
|
||||
`spotbugs-excludes.xml` with a justification in the PR. To run the static
|
||||
@@ -1639,7 +1654,7 @@ QWEN_MANAGED_HOOK_CGROUP_ROOT=/sys/fs/cgroup/your-delegated-root \
|
||||
-Dmysql.user=root -Dmysql.password= verify
|
||||
```
|
||||
|
||||
Missing Linux/MySQL/cgroup prerequisites are not acceptance. This selector verifies only the extension class; it does not establish that the complete Hosted profile passed. Evidence is saved before forced fixture teardown under `target/failsafe-reports/hosted-extension-evidence/` and is included in the existing CI report upload. The interrupted MCP case leaves its public Turn `RUNNING` with durable `ma_turn.error_code = hosted_runtime_recovery_blocked` and ordinary execution `UNKNOWN`, rejects new input and close with `409 turn_active`, and does not replay the physical effect; successful cleanup is established by the separate healthy-close case. This gate uses a controlled loopback model and local stdio transport, so it does not establish upstream-model, remote transport, reboot, cross-machine, SSH MCP/Hook, or production acceptance. See the complete [English design](../../docs/design/2026-10-16-managed-extension-acceptance.md) and [Chinese design](../../docs/design/2026-10-16-managed-extension-acceptance.zh-CN.md).
|
||||
Missing Linux/MySQL/cgroup prerequisites are not acceptance. This selector verifies only the extension class; it does not establish that the complete Hosted profile passed. Evidence is saved before forced fixture teardown under `verification/target/failsafe-reports/hosted-extension-evidence/` and is included in the existing CI report upload. The interrupted MCP case leaves its public Turn `RUNNING` with durable `ma_turn.error_code = hosted_runtime_recovery_blocked` and ordinary execution `UNKNOWN`, rejects new input and close with `409 turn_active`, and does not replay the physical effect; successful cleanup is established by the separate healthy-close case. This gate uses a controlled loopback model and local stdio transport, so it does not establish upstream-model, remote transport, reboot, cross-machine, SSH MCP/Hook, or production acceptance. See the complete [English design](../../docs/design/2026-10-16-managed-extension-acceptance.md) and [Chinese design](../../docs/design/2026-10-16-managed-extension-acceptance.zh-CN.md).
|
||||
|
||||
## Real-model end-to-end check
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>qwen-managed-agent-parent</artifactId>
|
||||
<version>0.1.0-alpha</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>qwen-managed-agent-adapters</artifactId>
|
||||
<name>Qwen Managed Agent Adapters</name>
|
||||
<properties>
|
||||
<managed.module.root>${project.basedir}/..</managed.module.root>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>qwen-managed-agent-contracts</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>qwencode-sdk</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.aliyun.oss</groupId>
|
||||
<artifactId>aliyun-sdk-oss</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.rocketmq</groupId>
|
||||
<artifactId>rocketmq-client-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.annotation</groupId>
|
||||
<artifactId>jakarta.annotation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.github.spotbugs</groupId>
|
||||
<artifactId>spotbugs-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<failIfNoSpecifiedTests>false</failIfNoSpecifiedTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
+18
-27
@@ -1,7 +1,5 @@
|
||||
package com.alibaba.qwen.code.managedagent.config;
|
||||
package com.alibaba.qwen.code.managedagent.adapters.artifact;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.artifact.AliyunToolPublicationObjectStore;
|
||||
import com.alibaba.qwen.code.managedagent.artifact.ToolPublicationObjectStore;
|
||||
import com.aliyun.oss.ClientBuilderConfiguration;
|
||||
import com.aliyun.oss.OSS;
|
||||
import com.aliyun.oss.OSSClientBuilder;
|
||||
@@ -9,26 +7,26 @@ import com.aliyun.oss.common.auth.CredentialsProvider;
|
||||
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
|
||||
import com.aliyun.oss.common.comm.SignVersion;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(prefix = "qwen.managed-agent.tool-publication",
|
||||
name = "enabled", havingValue = "true")
|
||||
@ConditionalOnBean(ToolPublicationConfiguration.class)
|
||||
@ConditionalOnMissingBean(ToolPublicationObjectStore.class)
|
||||
public class AliyunToolPublicationStorageAutoConfiguration {
|
||||
@Bean(destroyMethod = "shutdown")
|
||||
public OSS toolPublicationOss(ManagedAgentProperties properties) throws Exception {
|
||||
return buildOss(properties, CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider());
|
||||
public final class AliyunObjectStoreClient implements AutoCloseable {
|
||||
private final OSS client;
|
||||
public AliyunObjectStoreClient(OSS client) { this.client = client; }
|
||||
public static AliyunObjectStoreClient environment(com.alibaba.qwen.code.managedagent.settings.ManagedAgentSettings settings) throws Exception {
|
||||
return new AliyunObjectStoreClient(buildOss(settings, CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider()));
|
||||
}
|
||||
|
||||
public static OSS buildOss(ManagedAgentProperties properties, CredentialsProvider credentials) {
|
||||
public static AliyunObjectStoreClient regional(URI endpoint, String region) throws Exception {
|
||||
var configuration = new ClientBuilderConfiguration();
|
||||
configuration.setSignatureVersion(SignVersion.V4);
|
||||
AliyunToolPublicationObjectStore.configureClientRetries(configuration);
|
||||
return new AliyunObjectStoreClient(OSSClientBuilder.create().endpoint(endpoint.toString()).region(region)
|
||||
.credentialsProvider(CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider())
|
||||
.clientConfiguration(configuration).build());
|
||||
}
|
||||
public AliyunToolPublicationObjectStore objects(String bucket) { return new AliyunToolPublicationObjectStore(client, bucket); }
|
||||
public void shutdown() { client.shutdown(); }
|
||||
public void close() { shutdown(); }
|
||||
public static OSS buildOss(com.alibaba.qwen.code.managedagent.settings.ManagedAgentSettings properties, CredentialsProvider credentials) {
|
||||
var settings = properties.getToolPublication();
|
||||
URI endpoint = URI.create(required(settings.getOssEndpoint(), "OSS endpoint"));
|
||||
String region = required(settings.getOssRegion(), "OSS region");
|
||||
@@ -48,13 +46,6 @@ public class AliyunToolPublicationStorageAutoConfiguration {
|
||||
.clientConfiguration(client).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ToolPublicationObjectStore toolPublicationObjects(OSS toolPublicationOss,
|
||||
ManagedAgentProperties properties) {
|
||||
return new AliyunToolPublicationObjectStore(toolPublicationOss,
|
||||
properties.getToolPublication().getOssBucket());
|
||||
}
|
||||
|
||||
private static String required(String value, String label) {
|
||||
if (StringUtils.isBlank(value)) {
|
||||
throw new IllegalStateException("Tool publication " + label + " is required");
|
||||
+3
-1
@@ -1,5 +1,7 @@
|
||||
package com.alibaba.qwen.code.managedagent.artifact;
|
||||
package com.alibaba.qwen.code.managedagent.adapters.artifact;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.contracts.artifact.ToolPublicationObjectStore;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.artifact.ToolPublicationStorageException;
|
||||
import com.aliyun.oss.ClientConfiguration;
|
||||
import com.aliyun.oss.ClientException;
|
||||
import com.aliyun.oss.HttpMethod;
|
||||
+4
-1
@@ -1,5 +1,8 @@
|
||||
package com.alibaba.qwen.code.managedagent.event;
|
||||
package com.alibaba.qwen.code.managedagent.adapters.event;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.contracts.event.JournalEventTransport;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.event.ManagedEventEnvelope;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.event.ManagedEventEnvelopes;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.rocketmq.client.apis.ClientServiceProvider;
|
||||
+16
-5
@@ -1,5 +1,8 @@
|
||||
package com.alibaba.qwen.code.managedagent.event;
|
||||
package com.alibaba.qwen.code.managedagent.adapters.event;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.contracts.event.EventTransport;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.event.ManagedEventEnvelope;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.event.ManagedEventEnvelopes;
|
||||
import com.alibaba.qwen.code.managedagent.model.StoreModels.EventRecord;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.rocketmq.client.apis.ClientServiceProvider;
|
||||
@@ -63,9 +66,10 @@ public class LiteTopicPublicEventTransport implements EventTransport, Closeable
|
||||
ClientServiceProvider provider, String topic, ObjectMapper mapper,
|
||||
BiFunction<String, String, Optional<String>> workspaces) {
|
||||
this(producer, provider, topic, mapper, workspaces,
|
||||
TransportExecutors.bounded(Thread.ofPlatform().daemon()
|
||||
.name("event-transport-public").factory(),
|
||||
1, QUEUE_CAPACITY));
|
||||
new java.util.concurrent.ThreadPoolExecutor(1, 1, 0, java.util.concurrent.TimeUnit.MILLISECONDS,
|
||||
new java.util.concurrent.ArrayBlockingQueue<>(QUEUE_CAPACITY),
|
||||
Thread.ofPlatform().daemon().name("event-transport-public").factory(),
|
||||
new java.util.concurrent.ThreadPoolExecutor.AbortPolicy()));
|
||||
}
|
||||
|
||||
// The executor is injectable so a test can distribute synchronously.
|
||||
@@ -84,7 +88,14 @@ public class LiteTopicPublicEventTransport implements EventTransport, Closeable
|
||||
/** Stops the distribution thread; the shared producer is the config's. */
|
||||
@Override
|
||||
public void close() {
|
||||
TransportExecutors.close(executor);
|
||||
if (executor instanceof java.util.concurrent.ExecutorService service) {
|
||||
service.shutdown();
|
||||
try {
|
||||
service.awaitTermination(2, java.util.concurrent.TimeUnit.SECONDS);
|
||||
} catch (InterruptedException interrupted) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
package com.alibaba.qwen.code.managedagent.adapters.event;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.contracts.event.EventMessageListener;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.event.EventSubscriptions;
|
||||
import com.alibaba.qwen.code.managedagent.settings.ManagedAgentSettings;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.rocketmq.client.apis.ClientConfiguration;
|
||||
import org.apache.rocketmq.client.apis.ClientException;
|
||||
import org.apache.rocketmq.client.apis.ClientServiceProvider;
|
||||
import org.apache.rocketmq.client.apis.consumer.ConsumeResult;
|
||||
import org.apache.rocketmq.client.apis.consumer.LitePushConsumer;
|
||||
import org.apache.rocketmq.client.apis.producer.Producer;
|
||||
public final class RocketMqEventClients {
|
||||
public static final String CAPABILITY_PROBE_LITE = "capability-probe";
|
||||
private final ClientServiceProvider provider;
|
||||
public RocketMqEventClients() { this(ClientServiceProvider.loadService()); }
|
||||
public RocketMqEventClients(ClientServiceProvider provider) { this.provider = provider; }
|
||||
public static final class Publisher implements java.io.Closeable {
|
||||
private final Producer producer;
|
||||
private Publisher(Producer producer) { this.producer = producer; }
|
||||
public void close() throws java.io.IOException { producer.close(); }
|
||||
}
|
||||
public LiteTopicEventTransport journal(Publisher publisher, ManagedAgentSettings properties) {
|
||||
var config = properties.getEventTransport();
|
||||
return new LiteTopicEventTransport(publisher.producer, provider, config.getTopic(), config.getWakeBootstrapLiteTopic());
|
||||
}
|
||||
public LiteTopicPublicEventTransport publicEvents(Publisher publisher, ManagedAgentSettings properties,
|
||||
ObjectMapper json, java.util.function.BiFunction<String, String, java.util.Optional<String>> workspaceOf) {
|
||||
return new LiteTopicPublicEventTransport(publisher.producer, provider, properties.getEventTransport().getTopic(), json, workspaceOf);
|
||||
}
|
||||
public Publisher producer(ManagedAgentSettings properties) throws ClientException {
|
||||
var config = properties.getEventTransport();
|
||||
ClientConfiguration clientConfig = clientConfiguration(config);
|
||||
verifyLiteTopicCapability(provider, clientConfig, config.getTopic(),
|
||||
config.getCapabilityProbeGroup());
|
||||
Producer producer = provider.newProducerBuilder()
|
||||
.setClientConfiguration(clientConfig)
|
||||
.setTopics(config.getTopic())
|
||||
.build();
|
||||
try {
|
||||
verifyTopicAcceptsLiteMessages(provider, producer,
|
||||
config.getTopic());
|
||||
} catch (RuntimeException failure) {
|
||||
closeQuietly(producer, failure);
|
||||
throw failure;
|
||||
}
|
||||
return new Publisher(producer);
|
||||
}
|
||||
|
||||
public EventSubscriptions owner(
|
||||
ManagedAgentSettings properties,
|
||||
EventMessageListener listener,
|
||||
java.util.function.Consumer<EventSubscriptions> bind)
|
||||
throws ClientException {
|
||||
var config = properties.getEventTransport();
|
||||
LitePushConsumer consumer = provider.newLitePushConsumerBuilder()
|
||||
.setClientConfiguration(clientConfiguration(config))
|
||||
.setConsumerGroup(config.getOwnerGroup())
|
||||
.bindTopic(config.getTopic())
|
||||
.setMessageListener(message -> com.alibaba.qwen.code.managedagent.adapters.event.RocketMqMessages.consume(listener, message))
|
||||
.build();
|
||||
bind.accept(com.alibaba.qwen.code.managedagent.adapters.event.RocketMqMessages.subscriptions(consumer));
|
||||
return RocketMqMessages.subscriptions(consumer);
|
||||
}
|
||||
|
||||
public EventSubscriptions wake(ManagedAgentSettings properties, EventMessageListener listener)
|
||||
throws ClientException {
|
||||
var config = properties.getEventTransport();
|
||||
LitePushConsumer consumer = provider.newLitePushConsumerBuilder()
|
||||
.setClientConfiguration(clientConfiguration(config))
|
||||
.setConsumerGroup(config.getWakeBootstrapGroup())
|
||||
.bindTopic(config.getTopic())
|
||||
.setMessageListener(message -> com.alibaba.qwen.code.managedagent.adapters.event.RocketMqMessages.consume(listener, message))
|
||||
.build();
|
||||
try {
|
||||
consumer.subscribeLite(config.getWakeBootstrapLiteTopic());
|
||||
} catch (ClientException | RuntimeException failure) {
|
||||
closeQuietly(consumer, failure);
|
||||
throw new IllegalStateException("the event-transport wake"
|
||||
+ " bootstrap group " + config.getWakeBootstrapGroup()
|
||||
+ " could not subscribe "
|
||||
+ config.getWakeBootstrapLiteTopic() + " on topic "
|
||||
+ config.getTopic() + ": pre-create it as a LITE group"
|
||||
+ " bound to the topic (mqadmin updateSubGroup -c"
|
||||
+ " <cluster> -g " + config.getWakeBootstrapGroup()
|
||||
+ " -o true --attributes +lite.bind.topic="
|
||||
+ config.getTopic() + ")", failure);
|
||||
}
|
||||
return RocketMqMessages.subscriptions(consumer);
|
||||
}
|
||||
private static ClientConfiguration clientConfiguration(
|
||||
ManagedAgentSettings.EventTransport config) {
|
||||
return ClientConfiguration.newBuilder()
|
||||
.setEndpoints(config.getEndpoints())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static void closeQuietly(java.io.Closeable closeable,
|
||||
Throwable failure) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Exception suppressed) {
|
||||
failure.addSuppressed(suppressed);
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyLiteTopicCapability(ClientServiceProvider provider,
|
||||
ClientConfiguration clientConfig, String topic, String group) {
|
||||
try (LitePushConsumer probe = provider.newLitePushConsumerBuilder()
|
||||
.setClientConfiguration(clientConfig)
|
||||
.setConsumerGroup(group)
|
||||
.bindTopic(topic)
|
||||
.setMessageListener(messageView -> ConsumeResult.SUCCESS)
|
||||
.build()) {
|
||||
probe.subscribeLite(CAPABILITY_PROBE_LITE);
|
||||
probe.unsubscribeLite(CAPABILITY_PROBE_LITE);
|
||||
} catch (Exception failure) {
|
||||
throw new IllegalStateException("the event-transport LiteTopic"
|
||||
+ " capability probe failed on topic " + topic + ": the"
|
||||
+ " broker must serve LiteTopics (RocketMQ 5.5.0+ with"
|
||||
+ " enableLmq) and the probe group " + group
|
||||
+ " must be pre-created as a LITE group bound to the topic"
|
||||
+ " (mqadmin updateSubGroup -c <cluster> -g " + group
|
||||
+ " -o true --attributes +lite.bind.topic=" + topic
|
||||
+ "); check qwen.managed-agent.event-transport.endpoints",
|
||||
failure);
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyTopicAcceptsLiteMessages(
|
||||
ClientServiceProvider provider, Producer producer, String topic) {
|
||||
try {
|
||||
producer.send(provider.newMessageBuilder()
|
||||
.setTopic(topic)
|
||||
.setLiteTopic(CAPABILITY_PROBE_LITE)
|
||||
.setKeys(CAPABILITY_PROBE_LITE)
|
||||
.setBody("{}".getBytes(java.nio.charset.StandardCharsets.UTF_8))
|
||||
.build());
|
||||
} catch (ClientException | RuntimeException failure) {
|
||||
throw new IllegalStateException("the event-transport topic "
|
||||
+ topic + " refused a LiteTopic message: create it as a"
|
||||
+ " LITE topic (mqadmin updateTopic -c <cluster> -t "
|
||||
+ topic + " -a +message.type=LITE)", failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.alibaba.qwen.code.managedagent.adapters.event;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.contracts.event.EventMessageListener;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.event.EventSubscriptions;
|
||||
import org.apache.rocketmq.client.apis.consumer.ConsumeResult;
|
||||
import org.apache.rocketmq.client.apis.consumer.LitePushConsumer;
|
||||
import org.apache.rocketmq.client.apis.message.MessageView;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
public final class RocketMqMessages {
|
||||
private RocketMqMessages() { }
|
||||
public static ConsumeResult consume(EventMessageListener listener, MessageView message) {
|
||||
var buffer = message.getBody().duplicate();
|
||||
byte[] bytes = new byte[buffer.remaining()];
|
||||
buffer.get(bytes);
|
||||
listener.consume(bytes, message.getMessageId().toString());
|
||||
return ConsumeResult.SUCCESS;
|
||||
}
|
||||
public static EventSubscriptions subscriptions(LitePushConsumer consumer) {
|
||||
return new EventSubscriptions() {
|
||||
public Set<String> getLiteTopicSet() { return consumer.getLiteTopicSet(); }
|
||||
public void subscribeLite(String topic) throws Exception { consumer.subscribeLite(topic); }
|
||||
public void unsubscribeLite(String topic) throws Exception { consumer.unsubscribeLite(topic); }
|
||||
public void close() throws IOException { consumer.close(); }
|
||||
};
|
||||
}
|
||||
}
|
||||
+11
-11
@@ -1,11 +1,11 @@
|
||||
package com.alibaba.qwen.code.managedagent.extensions;
|
||||
package com.alibaba.qwen.code.managedagent.adapters.extensions;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.config.ManagedAgentProperties;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.extensions.ManagedExtensionPackages;
|
||||
import com.alibaba.qwen.code.managedagent.exception.ManagedAgentException;
|
||||
import com.alibaba.qwen.code.runtimebroker.managedschema.ManagedSchemaKeys;
|
||||
import com.alibaba.qwen.code.managedagent.settings.ManagedAgentSettings;
|
||||
import com.alibaba.qwen.code.runtimebroker.schema.ManagedSchemaKeys;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -19,10 +19,10 @@ import java.nio.file.StandardOpenOption;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
public class ManagedExtensionArchiveStore {
|
||||
private final ManagedAgentProperties properties;
|
||||
public class ManagedExtensionArchiveStore implements com.alibaba.qwen.code.managedagent.contracts.extensions.ExtensionArchives {
|
||||
private final ManagedAgentSettings properties;
|
||||
|
||||
public ManagedExtensionArchiveStore(ManagedAgentProperties properties) {
|
||||
public ManagedExtensionArchiveStore(ManagedAgentSettings properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public class ManagedExtensionArchiveStore {
|
||||
while ((count = source.read(buffer)) != -1) {
|
||||
length += count;
|
||||
if (length > ManagedExtensionPackages.MAX_ARCHIVE_BYTES) {
|
||||
throw new ManagedAgentException(HttpStatus.PAYLOAD_TOO_LARGE,
|
||||
throw new ManagedAgentException(413,
|
||||
"extension_package_too_large", "Extension archive exceeds 100 MiB.");
|
||||
}
|
||||
hash.update(buffer, 0, count);
|
||||
@@ -52,7 +52,7 @@ public class ManagedExtensionArchiveStore {
|
||||
}
|
||||
}
|
||||
if (length == 0 || !digest.equals(org.apache.commons.codec.binary.Hex.encodeHexString(hash.digest()))) {
|
||||
throw new ManagedAgentException(HttpStatus.BAD_REQUEST,
|
||||
throw new ManagedAgentException(400,
|
||||
"extension_package_digest_mismatch", "Extension archive digest does not match its contents.");
|
||||
}
|
||||
try (FileChannel file = FileChannel.open(temporary, StandardOpenOption.WRITE)) {
|
||||
@@ -90,7 +90,7 @@ public class ManagedExtensionArchiveStore {
|
||||
throw unavailable();
|
||||
}
|
||||
if (digest == null || !digest.matches("[a-f0-9]{64}")) {
|
||||
throw new ManagedAgentException(HttpStatus.BAD_REQUEST, "invalid_request", "Invalid extension digest.");
|
||||
throw new ManagedAgentException(400, "invalid_request", "Invalid extension digest.");
|
||||
}
|
||||
Path root = Path.of(properties.getExtensions().getArtifactDirectory()).toAbsolutePath().normalize();
|
||||
if (Files.exists(root, LinkOption.NOFOLLOW_LINKS) && Files.isSymbolicLink(root)) {
|
||||
@@ -115,7 +115,7 @@ public class ManagedExtensionArchiveStore {
|
||||
}
|
||||
|
||||
private static ManagedAgentException unavailable() {
|
||||
return new ManagedAgentException(HttpStatus.SERVICE_UNAVAILABLE,
|
||||
return new ManagedAgentException(503,
|
||||
"extension_package_unavailable", "The saved extension archive is unavailable.");
|
||||
}
|
||||
}
|
||||
+17
-21
@@ -1,12 +1,11 @@
|
||||
package com.alibaba.qwen.code.managedagent.workspace.runtime;
|
||||
package com.alibaba.qwen.code.managedagent.adapters.runtime;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.config.ManagedAgentProperties;
|
||||
import com.alibaba.qwen.code.managedagent.config.ManagedAgentProperties.RuntimeBroker.ResidentWorkspace;
|
||||
import com.alibaba.qwen.code.managedagent.persistence.ResidentWorkspaces;
|
||||
import com.alibaba.qwen.code.managedagent.persistence.WorkspaceCsiReservationStore;
|
||||
import com.alibaba.qwen.code.managedagent.persistence.WorkspaceExecutionStore;
|
||||
import com.alibaba.qwen.code.runtimebroker.JdbcRuntimeBindingRepository;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.workspace.ResidentWorkspaceCatalog;
|
||||
import com.alibaba.qwen.code.managedagent.repository.WorkspaceCsiReservations;
|
||||
import com.alibaba.qwen.code.managedagent.settings.ManagedAgentSettings;
|
||||
import com.alibaba.qwen.code.managedagent.settings.ManagedAgentSettings.RuntimeBroker.ResidentWorkspace;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeBindingRecord;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeBindingRepository;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeLease;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeObservation;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeProvisionRequest;
|
||||
@@ -15,8 +14,6 @@ import com.alibaba.qwen.code.runtimebroker.RuntimeProvisioner;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeResourceHandle;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeScope;
|
||||
import com.alibaba.qwen.code.runtimebroker.provisioning.KubernetesHttpRuntimeClient;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
@@ -25,27 +22,26 @@ import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
final class ResidentWorkspaceProvisioner implements RuntimeProvisioner {
|
||||
private final ResidentWorkspaces residents;
|
||||
public final class ResidentWorkspaceProvisioner implements RuntimeProvisioner {
|
||||
private final ResidentWorkspaceCatalog residents;
|
||||
private final Map<ResidentWorkspace, WorkspaceCsiRuntimeProvisioner> providers = new LinkedHashMap<>();
|
||||
|
||||
ResidentWorkspaceProvisioner(ManagedAgentProperties properties, JdbcTemplate jdbc,
|
||||
PlatformTransactionManager manager, JdbcRuntimeBindingRepository bindings) {
|
||||
public ResidentWorkspaceProvisioner(ManagedAgentSettings properties, WorkspaceCsiReservations storage,
|
||||
RuntimeBindingRepository bindings) {
|
||||
var config = properties.getRuntimeBroker();
|
||||
residents = new ResidentWorkspaces(config.getResidentWorkspaces());
|
||||
residents = new ResidentWorkspaceCatalog(config.getResidentWorkspaces());
|
||||
if (config.getResidentWorkspaces().isEmpty() || !"workspace".equals(config.getIsolationClass())
|
||||
|| config.isTrustedLocalRebootRecovery() || config.isVerifiedWorkspaceRecoveryEnabled()
|
||||
|| config.isChildWorkspacesEnabled() || !config.getWorkspaceMounts().isEmpty()
|
||||
|| !properties.getHarness().isWorkspaceFilesEnabled() || properties.getHarness().isWorkspaceShellEnabled()
|
||||
|| config.getResidentKubernetesApiUrl().isBlank() || config.getResidentKubernetesTokenFile().isBlank()
|
||||
|| config.getResidentKubernetesCaFile().isBlank() || !bindings.usesDataSource(jdbc.getDataSource())) {
|
||||
|| config.getResidentKubernetesCaFile().isBlank() || !storage.usesSameDataSource(bindings)) {
|
||||
throw new IllegalStateException("Resident Workspace requires exclusive CSI/files configuration");
|
||||
}
|
||||
var api = new KubernetesHttpRuntimeClient(URI.create(config.getResidentKubernetesApiUrl()),
|
||||
Path.of(config.getResidentKubernetesTokenFile()), Path.of(config.getResidentKubernetesCaFile()));
|
||||
var storage = new WorkspaceCsiReservationStore(jdbc, manager, new com.fasterxml.jackson.databind.ObjectMapper());
|
||||
for (var selected : config.getResidentWorkspaces()) {
|
||||
ResidentWorkspaces.requireRegistration(jdbc, selected);
|
||||
storage.requireRegistration(selected);
|
||||
var guard = new WorkspaceCsiResourceGuard(api, selected.registration().clusterDomain(),
|
||||
selected.registration(), selected.protection());
|
||||
providers.put(selected, new WorkspaceCsiRuntimeProvisioner(storage, bindings, selected.registration(),
|
||||
@@ -57,19 +53,19 @@ final class ResidentWorkspaceProvisioner implements RuntimeProvisioner {
|
||||
var mapping = residents.require(request.getScope());
|
||||
if (request.getIsolationKey() != null || !mapping.registration().storageId().equals(request.getStorageId())
|
||||
|| !kind().equals(request.getProvisionerKind())) {
|
||||
throw WorkspaceExecutionStore.unavailable();
|
||||
throw com.alibaba.qwen.code.managedagent.repository.WorkspaceExecutionStore.unavailable();
|
||||
}
|
||||
return providers.get(mapping);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String kind() { return WorkspaceCsiReservationStore.PROVISIONER_KIND; }
|
||||
public String kind() { return WorkspaceCsiReservations.PROVISIONER_KIND; }
|
||||
|
||||
@Override
|
||||
public RuntimeProvisionRequest createRequest(RuntimeScope scope, String isolationKey) {
|
||||
var mapping = residents.require(scope);
|
||||
if (isolationKey != null) {
|
||||
throw WorkspaceExecutionStore.unavailable();
|
||||
throw com.alibaba.qwen.code.managedagent.repository.WorkspaceExecutionStore.unavailable();
|
||||
}
|
||||
return new RuntimeProvisionRequest(scope, null, kind(), mapping.registration().storageId());
|
||||
}
|
||||
@@ -106,7 +102,7 @@ final class ResidentWorkspaceProvisioner implements RuntimeProvisioner {
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> release(RuntimeProvisionRequest request, RuntimeLease lease) {
|
||||
return java.util.concurrent.CompletableFuture.failedFuture(WorkspaceExecutionStore.unavailable());
|
||||
return java.util.concurrent.CompletableFuture.failedFuture(com.alibaba.qwen.code.managedagent.repository.WorkspaceExecutionStore.unavailable());
|
||||
}
|
||||
|
||||
@Override
|
||||
+14
-29
@@ -1,37 +1,30 @@
|
||||
package com.alibaba.qwen.code.managedagent.persistence;
|
||||
package com.alibaba.qwen.code.managedagent.adapters.runtime;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.config.ManagedAgentProperties.RuntimeBroker.ResidentWorkspace;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.workspace.ResidentReclamation.*;
|
||||
import com.alibaba.qwen.code.managedagent.settings.ManagedAgentSettings.RuntimeBroker.ResidentWorkspace;
|
||||
import com.alibaba.qwen.code.runtimebroker.BrokerValues;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeBindingRecord;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeBrokerException;
|
||||
import com.alibaba.qwen.code.runtimebroker.provisioning.KubernetesHttpRuntimeClient;
|
||||
import com.alibaba.qwen.code.runtimebroker.spi.KubernetesMaintenanceClient;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.alibaba.qwen.code.managedagent.contracts.workspace.ResidentReclamation.DATA_POLICY;
|
||||
import static com.alibaba.qwen.code.runtimebroker.WorkspaceCsiRuntimeIdentity.digest;
|
||||
import static com.alibaba.qwen.code.runtimebroker.WorkspaceCsiRuntimeIdentity.map;
|
||||
import static com.alibaba.qwen.code.runtimebroker.WorkspaceCsiRuntimeIdentity.text;
|
||||
|
||||
/** Normal platform cleanup inside an exclusive Managed namespace; never an exceptional fence. */
|
||||
final class ResidentWorkspaceReclamation {
|
||||
static final String DATA_POLICY = "retain-same-tenant-admin-handoff/1";
|
||||
|
||||
record Baseline(String finalizer, String bootId, String nodeResourceVersion, String nodeHeartbeat,
|
||||
String leaseUid, String leaseResourceVersion, String leaseRenewTime, String attachmentUid) { }
|
||||
|
||||
record Receipt(String resourceIdentity, String baselineDigest, String podResourceVersion,
|
||||
String deletionTimestamp, String finishedAt, String nodeResourceVersion, String nodeHeartbeat,
|
||||
String leaseResourceVersion, String leaseRenewTime, String observedAt, String dataPolicy) { }
|
||||
|
||||
public final class ResidentWorkspaceReclamation {
|
||||
private record Node(Map<String, Object> value, String bootId, String resourceVersion, Instant heartbeat) { }
|
||||
private record Lease(String uid, String resourceVersion, Instant renewed) { }
|
||||
|
||||
private ResidentWorkspaceReclamation() { }
|
||||
|
||||
static Baseline prepare(RuntimeBindingRecord original, ResidentWorkspace selected,
|
||||
String finalizer, KubernetesHttpRuntimeClient api, Instant now) {
|
||||
public static Baseline prepare(RuntimeBindingRecord original, ResidentWorkspace selected,
|
||||
String finalizer, KubernetesMaintenanceClient api, Instant now) {
|
||||
var placement = map(original.getResourceHandle().getValue().get("placement"));
|
||||
var node = node(placement, api, now, false);
|
||||
var lease = lease(placement, api, now);
|
||||
@@ -48,8 +41,8 @@ final class ResidentWorkspaceReclamation {
|
||||
lease.uid(), lease.resourceVersion(), lease.renewed().toString(), text(metadata, "uid", 128));
|
||||
}
|
||||
|
||||
static Receipt complete(RuntimeBindingRecord original, ResidentWorkspace selected, Baseline baseline,
|
||||
Map<String, Object> pod, KubernetesHttpRuntimeClient api, Instant now) {
|
||||
public static Receipt complete(RuntimeBindingRecord original, ResidentWorkspace selected, Baseline baseline,
|
||||
Map<String, Object> pod, KubernetesMaintenanceClient api, Instant now) {
|
||||
var handle = original.getResourceHandle().getValue();
|
||||
var placement = map(handle.get("placement"));
|
||||
require(pod != null);
|
||||
@@ -92,15 +85,7 @@ final class ResidentWorkspaceReclamation {
|
||||
lease.resourceVersion(), lease.renewed().toString(), now.toString(), DATA_POLICY);
|
||||
}
|
||||
|
||||
static void verifyReceipt(RuntimeBindingRecord original, Baseline baseline, Receipt receipt) {
|
||||
require(receipt != null && receipt.resourceIdentity().equals(original.getResourceHandle().getValue().get("identity"))
|
||||
&& receipt.baselineDigest().equals(digest(baseline)) && DATA_POLICY.equals(receipt.dataPolicy())
|
||||
&& receipt.observedAt() != null && receipt.finishedAt() != null && receipt.deletionTimestamp() != null
|
||||
&& !Instant.parse(receipt.observedAt()).isBefore(Instant.parse(receipt.finishedAt())));
|
||||
Instant.parse(receipt.deletionTimestamp());
|
||||
}
|
||||
|
||||
private static Node node(Map<String, Object> placement, KubernetesHttpRuntimeClient api, Instant now, boolean fresh) {
|
||||
private static Node node(Map<String, Object> placement, KubernetesMaintenanceClient api, Instant now, boolean fresh) {
|
||||
var node = api.getCluster("nodes", (String) placement.get("nodeName")).toCompletableFuture().join();
|
||||
require(node != null);
|
||||
var metadata = map(node.get("metadata"));
|
||||
@@ -122,7 +107,7 @@ final class ResidentWorkspaceReclamation {
|
||||
text(metadata, "resourceVersion", 256), heartbeat);
|
||||
}
|
||||
|
||||
private static Lease lease(Map<String, Object> placement, KubernetesHttpRuntimeClient api, Instant now) {
|
||||
private static Lease lease(Map<String, Object> placement, KubernetesMaintenanceClient api, Instant now) {
|
||||
var lease = api.getNodeLease((String) placement.get("nodeName")).toCompletableFuture().join();
|
||||
require(lease != null);
|
||||
var metadata = map(lease.get("metadata"));
|
||||
@@ -140,7 +125,7 @@ final class ResidentWorkspaceReclamation {
|
||||
return new Lease(text(metadata, "uid", 128), text(metadata, "resourceVersion", 256), renewed);
|
||||
}
|
||||
|
||||
private static void exclusive(RuntimeBindingRecord original, ResidentWorkspace selected, KubernetesHttpRuntimeClient api) {
|
||||
private static void exclusive(RuntimeBindingRecord original, ResidentWorkspace selected, KubernetesMaintenanceClient api) {
|
||||
var registration = selected.registration();
|
||||
var physical = api.listPersistentVolumes().toCompletableFuture().join().stream().filter(pv -> {
|
||||
var spec = map(pv.get("spec"));
|
||||
@@ -159,7 +144,7 @@ final class ResidentWorkspaceReclamation {
|
||||
&& placement.get("podName").equals(map(consumers.getFirst().get("metadata")).get("name")));
|
||||
}
|
||||
|
||||
private static List<Map<String, Object>> attachments(ResidentWorkspace selected, KubernetesHttpRuntimeClient api) {
|
||||
private static List<Map<String, Object>> attachments(ResidentWorkspace selected, KubernetesMaintenanceClient api) {
|
||||
return api.listVolumeAttachments().toCompletableFuture().join().stream().filter(value -> {
|
||||
var spec = map(value.get("spec"));
|
||||
if (!selected.registration().pvName().equals(map(spec.get("source")).get("persistentVolumeName"))) {
|
||||
+5
-21
@@ -1,8 +1,10 @@
|
||||
package com.alibaba.qwen.code.managedagent.workspace.runtime;
|
||||
package com.alibaba.qwen.code.managedagent.adapters.runtime;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.persistence.WorkspaceCsiRegistration;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.persistence.WorkspaceCsiRegistration;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.runtime.ProtectionIdentity;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.runtime.Receipt;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeBrokerException;
|
||||
import com.alibaba.qwen.code.runtimebroker.provisioning.KubernetesRuntimeClient;
|
||||
import com.alibaba.qwen.code.runtimebroker.spi.KubernetesRuntimeClient;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -20,24 +22,6 @@ public final class WorkspaceCsiResourceGuard {
|
||||
private final WorkspaceCsiRegistration registration;
|
||||
private final ProtectionIdentity protection;
|
||||
|
||||
public record ProtectionIdentity(String namespaceUid, String policyName, String policyUid,
|
||||
long policyGeneration, String bindingName, String bindingUid, long bindingGeneration) {
|
||||
public ProtectionIdentity {
|
||||
for (String value : List.of(namespaceUid, policyName, policyUid, bindingName, bindingUid)) {
|
||||
if (value.isBlank() || value.length() > 253 || value.codePoints().anyMatch(point -> point < 33)) {
|
||||
throw new IllegalArgumentException("Invalid CSI protection identity");
|
||||
}
|
||||
}
|
||||
if (policyGeneration < 1 || bindingGeneration < 1) {
|
||||
throw new IllegalArgumentException("Invalid CSI protection generation");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record Receipt(String physicalKey, long registrationRevision, String namespaceUid,
|
||||
String pvcResourceVersion, String pvResourceVersion, ProtectionIdentity protection) {
|
||||
}
|
||||
|
||||
public WorkspaceCsiResourceGuard(KubernetesRuntimeClient api, String trustedClusterDomain,
|
||||
WorkspaceCsiRegistration registration, ProtectionIdentity protection) {
|
||||
this.api = Objects.requireNonNull(api);
|
||||
+25
-34
@@ -1,9 +1,10 @@
|
||||
package com.alibaba.qwen.code.managedagent.workspace.runtime;
|
||||
package com.alibaba.qwen.code.managedagent.adapters.runtime;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.persistence.WorkspaceCsiRegistration;
|
||||
import com.alibaba.qwen.code.managedagent.persistence.WorkspaceCsiReservationStore;
|
||||
import com.alibaba.qwen.code.runtimebroker.JdbcRuntimeBindingRepository;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.persistence.WorkspaceCsiRegistration;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.runtime.WorkerArtifact;
|
||||
import com.alibaba.qwen.code.managedagent.repository.WorkspaceCsiReservations;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeBindingRecord;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeBindingRepository;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeBrokerException;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeLease;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeObservation;
|
||||
@@ -14,13 +15,11 @@ import com.alibaba.qwen.code.runtimebroker.RuntimeResourceHandle;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeScope;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeTransport;
|
||||
import com.alibaba.qwen.code.runtimebroker.WorkspaceCsiRuntimeIdentity;
|
||||
import com.alibaba.qwen.code.runtimebroker.provisioning.KubernetesRuntimeClient;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.HttpRuntimeTransport;
|
||||
import com.alibaba.qwen.code.runtimebroker.adapter.HttpRuntimeTransport;
|
||||
import com.alibaba.qwen.code.runtimebroker.spi.KubernetesRuntimeClient;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.ManagedCsiFilesProtocol;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
@@ -61,8 +60,8 @@ import static com.alibaba.qwen.code.runtimebroker.WorkspaceCsiRuntimeIdentity.uu
|
||||
/** Private CSI placement; ambiguous creation never authorizes adoption or replacement. */
|
||||
public final class WorkspaceCsiRuntimeProvisioner implements RuntimeProvisioner {
|
||||
private static final String BOOT_PATH = "/var/run/qwen-runtime/boot.json";
|
||||
private final WorkspaceCsiReservationStore storage;
|
||||
private final JdbcRuntimeBindingRepository bindings;
|
||||
private final WorkspaceCsiReservations storage;
|
||||
private final RuntimeBindingRepository bindings;
|
||||
private final WorkspaceCsiRegistration registration;
|
||||
private final KubernetesRuntimeClient api;
|
||||
private final WorkspaceCsiResourceGuard guard;
|
||||
@@ -77,49 +76,41 @@ public final class WorkspaceCsiRuntimeProvisioner implements RuntimeProvisioner
|
||||
private final Map<RuntimeProvisionSeed, RuntimeBindingRecord> claims = new ConcurrentHashMap<>();
|
||||
private final Map<RuntimeProvisionSeed, Placement> placements = new ConcurrentHashMap<>();
|
||||
|
||||
public record WorkerArtifact(String configMapName, String uid, String sha256) {
|
||||
public WorkerArtifact {
|
||||
dns(Objects.requireNonNull(configMapName));
|
||||
uuid(Objects.requireNonNull(uid));
|
||||
require(sha256 != null && sha256.matches("[0-9a-f]{64}"));
|
||||
}
|
||||
}
|
||||
|
||||
public WorkspaceCsiRuntimeProvisioner(WorkspaceCsiReservationStore storage,
|
||||
JdbcRuntimeBindingRepository bindings, WorkspaceCsiRegistration registration) {
|
||||
public WorkspaceCsiRuntimeProvisioner(WorkspaceCsiReservations storage,
|
||||
RuntimeBindingRepository bindings, WorkspaceCsiRegistration registration) {
|
||||
this(storage, bindings, registration, null, null, null, List.of(), Duration.ofSeconds(30), List.of(), new HttpRuntimeTransport());
|
||||
}
|
||||
|
||||
public WorkspaceCsiRuntimeProvisioner(WorkspaceCsiReservationStore storage,
|
||||
JdbcRuntimeBindingRepository bindings, WorkspaceCsiRegistration registration, KubernetesRuntimeClient api,
|
||||
public WorkspaceCsiRuntimeProvisioner(WorkspaceCsiReservations storage,
|
||||
RuntimeBindingRepository bindings, WorkspaceCsiRegistration registration, KubernetesRuntimeClient api,
|
||||
WorkspaceCsiResourceGuard guard, String image, List<String> command, Duration timeout) {
|
||||
this(storage, bindings, registration, api, guard, image, command, timeout, List.of());
|
||||
}
|
||||
|
||||
public WorkspaceCsiRuntimeProvisioner(WorkspaceCsiReservationStore storage,
|
||||
JdbcRuntimeBindingRepository bindings, WorkspaceCsiRegistration registration, KubernetesRuntimeClient api,
|
||||
public WorkspaceCsiRuntimeProvisioner(WorkspaceCsiReservations storage,
|
||||
RuntimeBindingRepository bindings, WorkspaceCsiRegistration registration, KubernetesRuntimeClient api,
|
||||
WorkspaceCsiResourceGuard guard, String image, List<String> command, Duration timeout, List<WorkerArtifact> artifacts) {
|
||||
this(storage, bindings, registration, Objects.requireNonNull(api), Objects.requireNonNull(guard),
|
||||
image, command, timeout, artifacts, new HttpRuntimeTransport());
|
||||
}
|
||||
|
||||
public WorkspaceCsiRuntimeProvisioner(WorkspaceCsiReservationStore storage,
|
||||
JdbcRuntimeBindingRepository bindings, WorkspaceCsiRegistration registration, KubernetesRuntimeClient api,
|
||||
public WorkspaceCsiRuntimeProvisioner(WorkspaceCsiReservations storage,
|
||||
RuntimeBindingRepository bindings, WorkspaceCsiRegistration registration, KubernetesRuntimeClient api,
|
||||
WorkspaceCsiResourceGuard guard, String image, List<String> command, Duration timeout,
|
||||
List<WorkerArtifact> artifacts, String authorityOrigin) {
|
||||
this(storage, bindings, registration, Objects.requireNonNull(api), Objects.requireNonNull(guard), image, command,
|
||||
timeout, artifacts, new HttpRuntimeTransport(), ManagedCsiFilesProtocol.authority(authorityOrigin));
|
||||
}
|
||||
|
||||
WorkspaceCsiRuntimeProvisioner(WorkspaceCsiReservationStore storage,
|
||||
JdbcRuntimeBindingRepository bindings, WorkspaceCsiRegistration registration, KubernetesRuntimeClient api,
|
||||
WorkspaceCsiRuntimeProvisioner(WorkspaceCsiReservations storage,
|
||||
RuntimeBindingRepository bindings, WorkspaceCsiRegistration registration, KubernetesRuntimeClient api,
|
||||
WorkspaceCsiResourceGuard guard, String image, List<String> command, Duration timeout,
|
||||
List<WorkerArtifact> artifacts, RuntimeTransport transport) {
|
||||
this(storage, bindings, registration, api, guard, image, command, timeout, artifacts, transport, null);
|
||||
}
|
||||
|
||||
WorkspaceCsiRuntimeProvisioner(WorkspaceCsiReservationStore storage,
|
||||
JdbcRuntimeBindingRepository bindings, WorkspaceCsiRegistration registration, KubernetesRuntimeClient api,
|
||||
WorkspaceCsiRuntimeProvisioner(WorkspaceCsiReservations storage,
|
||||
RuntimeBindingRepository bindings, WorkspaceCsiRegistration registration, KubernetesRuntimeClient api,
|
||||
WorkspaceCsiResourceGuard guard, String image, List<String> command, Duration timeout,
|
||||
List<WorkerArtifact> artifacts, RuntimeTransport transport, Map<String, Object> authority) {
|
||||
this.storage = Objects.requireNonNull(storage);
|
||||
@@ -145,7 +136,7 @@ public final class WorkspaceCsiRuntimeProvisioner implements RuntimeProvisioner
|
||||
|
||||
@Override
|
||||
public String kind() {
|
||||
return WorkspaceCsiReservationStore.PROVISIONER_KIND;
|
||||
return WorkspaceCsiReservations.PROVISIONER_KIND;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -264,7 +255,7 @@ public final class WorkspaceCsiRuntimeProvisioner implements RuntimeProvisioner
|
||||
} catch (RuntimeException error) {
|
||||
forgetPlacement(request, seed, handle);
|
||||
return error instanceof RuntimeBrokerException failure && failure.isRetryable()
|
||||
|| error instanceof DataAccessException || error instanceof TransactionException
|
||||
|| error instanceof com.alibaba.qwen.code.runtimebroker.StoreFailureException
|
||||
? RuntimeObservation.unknown(handle) : RuntimeObservation.conflict(handle);
|
||||
}
|
||||
});
|
||||
@@ -523,7 +514,7 @@ public final class WorkspaceCsiRuntimeProvisioner implements RuntimeProvisioner
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> storageTuple(WorkspaceCsiReservationStore.Reservation reservation) {
|
||||
private Map<String, Object> storageTuple(WorkspaceCsiReservations.Reservation reservation) {
|
||||
var result = new LinkedHashMap<String, Object>();
|
||||
result.putAll(Map.of("clusterDomain", registration.clusterDomain(), "namespace", registration.namespace(),
|
||||
"pvcUid", registration.pvcUid(), "pvUid", registration.pvUid(), "driver", registration.driver(),
|
||||
@@ -534,7 +525,7 @@ public final class WorkspaceCsiRuntimeProvisioner implements RuntimeProvisioner
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, Object> protection(WorkspaceCsiResourceGuard.Receipt receipt) {
|
||||
private Map<String, Object> protection(com.alibaba.qwen.code.managedagent.contracts.runtime.Receipt receipt) {
|
||||
require(registration.physicalKey().equals(receipt.physicalKey()) && registration.revision() == receipt.registrationRevision());
|
||||
var pin = receipt.protection();
|
||||
return Map.of("namespaceUid", pin.namespaceUid(), "policyName", pin.policyName(), "policyUid", pin.policyUid(),
|
||||
+8
-21
@@ -1,5 +1,6 @@
|
||||
package com.alibaba.qwen.code.managedagent.workspace;
|
||||
package com.alibaba.qwen.code.managedagent.adapters.workspace;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.contracts.workspace.ChildWorkspaceException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -41,11 +42,9 @@ import java.util.regex.Pattern;
|
||||
* repository's own config was read as data and found free of keys that
|
||||
* name a program (docs/design/2026-10-09-managed-child-workspace.md).
|
||||
*/
|
||||
public final class ChildWorktreeGit {
|
||||
public final class ChildWorktreeGit implements com.alibaba.qwen.code.managedagent.contracts.workspace.ChildWorktreeGit {
|
||||
/** The reserved directory below the storage root that holds every child Workspace. */
|
||||
public static final String CONTAINER = ".qwen-child-workspaces";
|
||||
static final String PIN_PREFIX = "refs/qwen/child-workspaces/";
|
||||
static final int MAX_CONFLICT_PATHS = 100;
|
||||
|
||||
private static final int MIN_MAJOR = 2;
|
||||
private static final int MIN_MINOR = 40;
|
||||
private static final String GITLINK = "160000";
|
||||
@@ -102,19 +101,10 @@ public final class ChildWorktreeGit {
|
||||
}
|
||||
|
||||
/** The located repository a step works in, every path real and inside the storage root. */
|
||||
public record Repository(Path top, Path gitDir, Path commonDir) {
|
||||
}
|
||||
|
||||
/** Where the parent's repository lies, relative to the storage root and to its own top. */
|
||||
public record Layout(String repositoryRelative, String offset) {
|
||||
}
|
||||
|
||||
/** The three-way merge of decision 5: a clean tree, or the conflicting paths. */
|
||||
public record Merge(String parentTree, String mergedTree, List<String> conflicts) {
|
||||
public boolean clean() {
|
||||
return conflicts.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Git version, refused below 2.40, the first release whose
|
||||
@@ -175,15 +165,8 @@ public final class ChildWorktreeGit {
|
||||
}
|
||||
|
||||
/** The child Workspace's directory, relative to the storage root. */
|
||||
public static String childDirectory(String childWorkspaceId) {
|
||||
return CONTAINER + "/" + childWorkspaceId;
|
||||
}
|
||||
|
||||
/** The child Session's working directory, relative to the storage root. */
|
||||
public static String childCwd(String childWorkspaceId, String offset) {
|
||||
return ".".equals(offset) ? childDirectory(childWorkspaceId)
|
||||
: childDirectory(childWorkspaceId) + "/" + offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates the repository of the parent's working directory and checks
|
||||
@@ -1558,4 +1541,8 @@ public final class ChildWorktreeGit {
|
||||
// Scratch only.
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public <T> T runWhileLive(BooleanSupplier live, Supplier<T> work) {
|
||||
return whileLive(live, work);
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package com.alibaba.qwen.code.managedagent.adapters.workspace;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.contracts.workspace.RecoveryWorker;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.alibaba.qwen.code.managedagent.contracts.workspace.WorkspaceRecoveryProtocol.check;
|
||||
import static com.alibaba.qwen.code.managedagent.contracts.workspace.WorkspaceRecoveryProtocol.text;
|
||||
public final class LocalRecoveryWorker implements RecoveryWorker {
|
||||
private static final com.fasterxml.jackson.databind.ObjectMapper JSON = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
|
||||
public void execute(JsonNode request, Session store,
|
||||
java.util.function.BiFunction<String, JsonNode, JsonNode> calls) throws Exception {
|
||||
var command = new ProcessBuilder(text(request, "nodeExecutable"), text(request, "cliEntry"),
|
||||
"--workspace-recovery-worker").redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
command.environment().keySet().removeIf(key -> key.startsWith("W1_") || key.startsWith("OSS_"));
|
||||
Process worker;
|
||||
try {
|
||||
worker = command.start();
|
||||
} catch (java.io.IOException error) {
|
||||
store.workerFailed();
|
||||
throw error;
|
||||
}
|
||||
try (var output = new BufferedInputStream(worker.getInputStream());
|
||||
var replies = new BufferedWriter(new OutputStreamWriter(worker.getOutputStream(), StandardCharsets.UTF_8))) {
|
||||
long lastId = 0;
|
||||
byte[] line;
|
||||
while ((line = message(output)) != null) {
|
||||
JsonNode call = com.alibaba.qwen.code.managedagent.contracts.workspace.WorkspaceRecoveryProtocol.parse(line);
|
||||
long callId = com.alibaba.qwen.code.managedagent.contracts.workspace.WorkspaceRecoveryProtocol.positive(call, "id");
|
||||
check(callId == lastId + 1, "worker_protocol_error");
|
||||
lastId = callId;
|
||||
ObjectNode response = JSON.createObjectNode().put("id", callId);
|
||||
try {
|
||||
response.set("result", calls.apply(text(call, "method"), call.path("params")));
|
||||
} catch (RuntimeException error) {
|
||||
String code = error instanceof com.alibaba.qwen.code.managedagent.contracts.workspace.WorkspaceRecoveryProtocol.RecoveryFailure failure
|
||||
? failure.code : "recovery_read_failed";
|
||||
if (!(error instanceof com.alibaba.qwen.code.managedagent.contracts.workspace.WorkspaceRecoveryProtocol.RecoveryFailure)) {
|
||||
store.workerFailed();
|
||||
}
|
||||
response.putObject("error").put("code", code).put("message", "Workspace recovery: " + code);
|
||||
}
|
||||
replies.write(response.toString());
|
||||
replies.newLine();
|
||||
replies.flush();
|
||||
}
|
||||
check(worker.waitFor(30, TimeUnit.SECONDS) && worker.exitValue() == 0, "worker_failed");
|
||||
JsonNode receipt = store.inspect();
|
||||
check(Set.of("SEALED", "VERIFIED").contains(receipt.path("state").asText()), "worker_incomplete");
|
||||
} catch (Exception error) {
|
||||
store.workerFailed();
|
||||
throw error;
|
||||
} finally {
|
||||
if (worker.isAlive()) {
|
||||
worker.destroyForcibly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] message(InputStream input) throws java.io.IOException {
|
||||
var bytes = new ByteArrayOutputStream();
|
||||
int next;
|
||||
while ((next = input.read()) != -1 && next != '\n') {
|
||||
check(bytes.size() < 16 * 1024 * 1024, "worker_message_too_large");
|
||||
bytes.write(next);
|
||||
}
|
||||
check(next != -1 || bytes.size() == 0, "worker_protocol_error");
|
||||
return next == -1 ? null : bytes.toByteArray();
|
||||
}
|
||||
|
||||
public void validateStateDirectory(java.nio.file.Path directory) throws java.io.IOException {
|
||||
com.alibaba.qwen.code.runtimebroker.provisioning.LocalProcessRuntimeProvisioner.validateStateDirectory(directory);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.alibaba.qwen.code.managedagent.adapters.workspace;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.workspace.WorktreeFiles;
|
||||
import com.alibaba.qwen.code.managedagent.repository.SessionWorktreeStore.Plan;
|
||||
|
||||
import java.nio.file.Path;
|
||||
public final class LocalWorktreeFiles implements WorktreeFiles {
|
||||
public String verify(Plan plan, String savedIdentity) { return SessionWorktreeGit.verify(plan, savedIdentity); }
|
||||
public void requireRoot(Plan plan) { SessionWorktreeGit.requireRoot(plan); }
|
||||
public String currentBranch(String verifiedIdentity) { return SessionWorktreeGit.currentBranch(verifiedIdentity); }
|
||||
public String identity(Path path) { return SessionWorktreeGit.identity(path); }
|
||||
}
|
||||
+6
-8
@@ -1,9 +1,9 @@
|
||||
package com.alibaba.qwen.code.managedagent.workspace;
|
||||
package com.alibaba.qwen.code.managedagent.adapters.workspace;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.contracts.workspace.ChildWorkspaceException;
|
||||
import com.alibaba.qwen.code.managedagent.exception.ManagedAgentException;
|
||||
import com.alibaba.qwen.code.managedagent.persistence.SessionWorktreeStore.Plan;
|
||||
import com.alibaba.qwen.code.managedagent.repository.SessionWorktreeStore.Plan;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -16,7 +16,7 @@ import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class SessionWorktreeGit implements AutoCloseable {
|
||||
public final class SessionWorktreeGit implements com.alibaba.qwen.code.managedagent.contracts.workspace.SessionWorktreeOperations {
|
||||
public static final String CONTAINER = ".qwen-managed-worktrees";
|
||||
private static final ObjectMapper JSON = new ObjectMapper()
|
||||
.enable(com.fasterxml.jackson.databind.DeserializationFeature.USE_LONG_FOR_INTS);
|
||||
@@ -27,9 +27,6 @@ public final class SessionWorktreeGit implements AutoCloseable {
|
||||
git.requireSupportedVersion();
|
||||
}
|
||||
|
||||
public record Source(String repositoryRelative, String offset, String commonDirectory,
|
||||
String commonIdentity, String commit, List<String> branches) { }
|
||||
|
||||
public Source source(Path root, String cwd, String baseRef) {
|
||||
if (cwd.split("/")[0].equals(CONTAINER)) {
|
||||
throw error("worktree_source_private", "Select the original project directory.");
|
||||
@@ -318,11 +315,12 @@ public final class SessionWorktreeGit implements AutoCloseable {
|
||||
}
|
||||
|
||||
public static ManagedAgentException error(String code, String message) {
|
||||
return new ManagedAgentException(HttpStatus.CONFLICT, code, message);
|
||||
return new ManagedAgentException(409, code, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
git.close();
|
||||
}
|
||||
public <T> T runWhileLive(java.util.function.BooleanSupplier live, java.util.function.Supplier<T> work) { return git.runWhileLive(live, work); }
|
||||
}
|
||||
File renamed without changes.
+33
@@ -0,0 +1,33 @@
|
||||
package com.alibaba.qwen.code.managedagent.harness;
|
||||
|
||||
import com.alibaba.qwen.code.daemon.HostedHarnessClient;
|
||||
import com.alibaba.qwen.code.managedagent.harness.contracts.HarnessCapabilities;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
public final class HarnessNegotiation implements AutoCloseable {
|
||||
private final HostedHarnessClient client;
|
||||
public HarnessNegotiation(URI uri, String token, String digest) {
|
||||
this(uri, token, digest, null);
|
||||
}
|
||||
public HarnessNegotiation(URI uri, String token, String digest, Duration timeout) {
|
||||
try {
|
||||
var builder = HostedHarnessClient.builder().baseUri(uri).bearerToken(token).capabilityDigest(digest);
|
||||
if (timeout != null) {
|
||||
builder.connectTimeout(timeout).requestTimeout(timeout);
|
||||
}
|
||||
client = builder.build();
|
||||
} catch (RuntimeException failure) {
|
||||
throw HarnessSdkValues.runtime(failure);
|
||||
}
|
||||
}
|
||||
public HarnessCapabilities capabilities() {
|
||||
try {
|
||||
var value = client.capabilities();
|
||||
return new HarnessCapabilities(value.getCurrentProtocolVersion(), value.getSupportedProtocolVersions(), value.getBootId(), value.getInstanceId(), value.getCapabilityDigest(), value.getLifecycleProtocolVersion(), value.getFeatures(), value.getTitleProtocolVersion(), value.getLifecycleV2Profiles());
|
||||
} catch (RuntimeException failure) {
|
||||
throw HarnessSdkValues.runtime(failure);
|
||||
}
|
||||
}
|
||||
public void close() { client.close(); }
|
||||
}
|
||||
+12
-12
@@ -6,16 +6,16 @@ import com.alibaba.qwen.code.daemon.HostedHarnessClient;
|
||||
import com.alibaba.qwen.code.daemon.HostedHarnessGenerationException;
|
||||
import com.alibaba.qwen.code.daemon.HostedHarnessPlacement;
|
||||
import com.alibaba.qwen.code.daemon.MutationOutcomeUnknownException;
|
||||
import com.alibaba.qwen.code.managedagent.config.ManagedAgentProperties;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.harness.HarnessConnector;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.security.WriterCredentialPolicy;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.workspace.WorkspaceToolProfiles;
|
||||
import com.alibaba.qwen.code.managedagent.exception.ManagedAgentException;
|
||||
import com.alibaba.qwen.code.managedagent.model.StoreModels.OperationRecord;
|
||||
import com.alibaba.qwen.code.managedagent.persistence.HarnessPlacementStore;
|
||||
import com.alibaba.qwen.code.managedagent.persistence.HarnessPlacementStore.Placement;
|
||||
import com.alibaba.qwen.code.managedagent.persistence.ManagedSessionSkills;
|
||||
import com.alibaba.qwen.code.managedagent.security.WriterCredentialPolicy;
|
||||
import com.alibaba.qwen.code.managedagent.workspace.WorkspaceToolProfiles;
|
||||
import com.alibaba.qwen.code.managedagent.repository.HarnessPlacementStore;
|
||||
import com.alibaba.qwen.code.managedagent.repository.HarnessPlacementStore.Placement;
|
||||
import com.alibaba.qwen.code.managedagent.repository.ManagedSessionSkills;
|
||||
import com.alibaba.qwen.code.managedagent.settings.ManagedAgentSettings;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
@@ -33,7 +33,7 @@ import java.util.function.Function;
|
||||
|
||||
/** Sticky routing over independently fenced Hosted process incarnations. */
|
||||
public class HarnessPoolConnector implements HarnessConnector {
|
||||
private final ManagedAgentProperties properties;
|
||||
private final ManagedAgentSettings properties;
|
||||
private final HarnessPlacementStore placements;
|
||||
private final WriterCredentialPolicy credentials;
|
||||
private final HarnessDiscovery discovery;
|
||||
@@ -82,13 +82,13 @@ public class HarnessPoolConnector implements HarnessConnector {
|
||||
private record Selected(Placement placement, Member member, QwenHostedHarnessConnector.PoolCall call) {
|
||||
}
|
||||
|
||||
public HarnessPoolConnector(ManagedAgentProperties properties, HarnessPlacementStore placements,
|
||||
public HarnessPoolConnector(ManagedAgentSettings properties, HarnessPlacementStore placements,
|
||||
HarnessDiscovery discovery, Function<HarnessDiscovery.Endpoint, QwenHostedHarnessConnector> connectors,
|
||||
ManagedSessionSkills skills) {
|
||||
this(properties, placements, discovery, connectors, skills, HarnessTls.poolContext(properties.getHarness().getPool()), null);
|
||||
}
|
||||
|
||||
HarnessPoolConnector(ManagedAgentProperties properties, HarnessPlacementStore placements,
|
||||
HarnessPoolConnector(ManagedAgentSettings properties, HarnessPlacementStore placements,
|
||||
HarnessDiscovery discovery, Function<HarnessDiscovery.Endpoint, QwenHostedHarnessConnector> connectors,
|
||||
ManagedSessionSkills skills, SSLContext tls,
|
||||
Function<HarnessDiscovery.Endpoint, HostedHarnessClient> clients) {
|
||||
@@ -270,7 +270,7 @@ public class HarnessPoolConnector implements HarnessConnector {
|
||||
: placements.takeoverForLifecycle(operation, placement, candidate.endpoint.instanceId(), candidate.owner().bootId());
|
||||
}
|
||||
} catch (ManagedAgentException refusal) {
|
||||
if (!quiescingOwner || !member.reachable || refusal.getStatus() != HttpStatus.CONFLICT
|
||||
if (!quiescingOwner || !member.reachable || refusal.getStatus() != 409
|
||||
|| !"harness_owner_still_live".equals(refusal.getCode())) {
|
||||
throw refusal;
|
||||
}
|
||||
@@ -294,7 +294,7 @@ public class HarnessPoolConnector implements HarnessConnector {
|
||||
}
|
||||
|
||||
private static ManagedAgentException unavailable() {
|
||||
return new ManagedAgentException(HttpStatus.SERVICE_UNAVAILABLE, "harness_pool_unavailable", "No compatible Harness owner is available");
|
||||
return new ManagedAgentException(503, "harness_pool_unavailable", "No compatible Harness owner is available");
|
||||
}
|
||||
|
||||
private <T> T call(String tenant, String session, Function<QwenHostedHarnessConnector, T> action) {
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package com.alibaba.qwen.code.managedagent.harness;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.harness.contracts.*;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletionException;
|
||||
|
||||
final class HarnessSdkValues {
|
||||
private static final ObjectMapper JSON = new ObjectMapper();
|
||||
private HarnessSdkValues() { }
|
||||
|
||||
static HarnessRuntimeRecovery recovery(com.alibaba.qwen.code.daemon.HarnessRuntimeRecovery value) {
|
||||
return value == null ? null : new HarnessRuntimeRecovery(value.getPhase(), value.getCheckpointId(),
|
||||
value.getActivationId(), value.getExecutions().stream().map(execution ->
|
||||
new HarnessRuntimeRecovery.Execution(execution.getFunctionCallId(), execution.getToolName(),
|
||||
execution.getExecutionCallId(), execution.getRuntimeSessionId(), execution.getProgressCursor(),
|
||||
execution.getOutcome(), execution.getStatus())).toList(),
|
||||
value.hasUnknownOutcome(), value.isContinuationReady(), value.isCancellationReady());
|
||||
}
|
||||
|
||||
static Throwable failure(Throwable error) {
|
||||
if (error == null) {
|
||||
return null;
|
||||
}
|
||||
Throwable cause = failure(error.getCause());
|
||||
Throwable[] suppressed = java.util.Arrays.stream(error.getSuppressed())
|
||||
.map(HarnessSdkValues::failure).toArray(Throwable[]::new);
|
||||
boolean changed = cause != error.getCause()
|
||||
|| !java.util.Arrays.equals(suppressed, error.getSuppressed());
|
||||
Throwable mapped = mapFailure(error, cause);
|
||||
if (mapped == error && changed) {
|
||||
mapped = error instanceof CompletionException
|
||||
? new CompletionException(error.getMessage(), cause)
|
||||
: error instanceof java.util.concurrent.ExecutionException
|
||||
? new java.util.concurrent.ExecutionException(error.getMessage(), cause)
|
||||
: new HarnessFailure(error.getMessage(), cause);
|
||||
}
|
||||
if (mapped != error) {
|
||||
mapped.setStackTrace(error.getStackTrace());
|
||||
for (Throwable failure : suppressed) {
|
||||
mapped.addSuppressed(failure);
|
||||
}
|
||||
}
|
||||
return mapped;
|
||||
}
|
||||
|
||||
private static Throwable mapFailure(Throwable error, Throwable cause) {
|
||||
if (error instanceof com.alibaba.qwen.code.daemon.DaemonHttpException value) {
|
||||
return new HarnessHttpFailure(value.getMessage(), cause, value.getStatusCode(), value.getResponseBody(), bodyFields(value));
|
||||
}
|
||||
if (error instanceof com.alibaba.qwen.code.daemon.DaemonProtocolException value) {
|
||||
return new HarnessProtocolFailure(value.getMessage(), cause);
|
||||
}
|
||||
if (error instanceof com.alibaba.qwen.code.daemon.DaemonTransportException value) {
|
||||
return new HarnessTransportFailure(value.getMessage(), cause);
|
||||
}
|
||||
if (error instanceof com.alibaba.qwen.code.daemon.HostedHarnessGenerationException value) {
|
||||
return new HarnessGenerationFailure(value.getMessage(), cause, value.getExpectedBootId(), value.getActualBootId(), value.getCode());
|
||||
}
|
||||
if (error instanceof com.alibaba.qwen.code.daemon.HarnessSessionRefusedException value) {
|
||||
return new HarnessSessionRefusedFailure(value.getMessage(), cause, value.getStatusCode(), value.getCode());
|
||||
}
|
||||
if (error instanceof com.alibaba.qwen.code.daemon.HostedHarnessCapabilityMismatchException value) {
|
||||
return new HarnessCapabilityFailure(value.getMessage(), cause, value.getExpectedDigest(), value.getActualDigest(), value.getCode());
|
||||
}
|
||||
if (error instanceof com.alibaba.qwen.code.daemon.DaemonClientCapacityException value) {
|
||||
return new HarnessCapacityFailure(value.getMessage(), cause);
|
||||
}
|
||||
if (error instanceof com.alibaba.qwen.code.daemon.MutationOutcomeUnknownException value) {
|
||||
return new HarnessMutationUnknownFailure(value.getMessage(), cause, value.getOperation());
|
||||
}
|
||||
if (error instanceof com.alibaba.qwen.code.daemon.HostedHarnessSettleAnsweredException value) {
|
||||
return new HarnessSettleAnsweredFailure(value.getMessage(), cause);
|
||||
}
|
||||
if (error instanceof com.alibaba.qwen.code.daemon.DaemonException value) {
|
||||
return new HarnessFailure(value.getMessage(), cause);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
static RuntimeException runtime(RuntimeException error) {
|
||||
return (RuntimeException) failure(error);
|
||||
}
|
||||
|
||||
private static Map<String, String> bodyFields(com.alibaba.qwen.code.daemon.DaemonHttpException error) {
|
||||
Map<String, String> fields = new LinkedHashMap<>();
|
||||
try {
|
||||
var body = JSON.readTree(error.getResponseBody());
|
||||
if (body != null && body.isObject()) {
|
||||
body.fieldNames()
|
||||
.forEachRemaining(
|
||||
name -> {
|
||||
String value = error.getBodyField(name);
|
||||
if (value != null) {
|
||||
fields.put(name, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (java.io.IOException | RuntimeException malformed) {
|
||||
return Map.of();
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package com.alibaba.qwen.code.managedagent.harness;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.config.ManagedAgentProperties;
|
||||
import com.alibaba.qwen.code.managedagent.settings.ManagedAgentSettings;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
@@ -18,7 +18,7 @@ public final class HarnessTls {
|
||||
private HarnessTls() {
|
||||
}
|
||||
|
||||
public static SSLContext poolContext(ManagedAgentProperties.HarnessPool properties) {
|
||||
public static SSLContext poolContext(ManagedAgentSettings.HarnessPool properties) {
|
||||
if (properties.getKeyStoreFile().isBlank() && properties.getTrustStoreFile().isBlank()) {
|
||||
return null;
|
||||
}
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
package com.alibaba.qwen.code.managedagent.harness;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.config.ManagedAgentProperties;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.HttpRuntimeTransport;
|
||||
import com.alibaba.qwen.code.managedagent.settings.ManagedAgentSettings;
|
||||
import com.alibaba.qwen.code.runtimebroker.adapter.HttpRuntimeTransport;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
/** A bounded namespace-scoped list/watch; uncertainty never creates placement candidates. */
|
||||
public final class KubernetesHarnessDiscovery implements HarnessDiscovery {
|
||||
private static final int RESPONSE_LIMIT = 2 * 1024 * 1024;
|
||||
private final ManagedAgentProperties.HarnessPool properties;
|
||||
private final ManagedAgentSettings.HarnessPool properties;
|
||||
private final ObjectMapper json;
|
||||
private final URI origin;
|
||||
private final HttpClient client;
|
||||
@@ -43,13 +43,13 @@ public final class KubernetesHarnessDiscovery implements HarnessDiscovery {
|
||||
private volatile Snapshot snapshot = new Snapshot(List.of(), false);
|
||||
private volatile boolean closed;
|
||||
|
||||
public KubernetesHarnessDiscovery(ManagedAgentProperties.HarnessPool properties, ObjectMapper json) {
|
||||
public KubernetesHarnessDiscovery(ManagedAgentSettings.HarnessPool properties, ObjectMapper json) {
|
||||
this(properties, json, secureOrigin(properties.getKubernetesApiUrl()),
|
||||
HttpClient.newBuilder().sslContext(HarnessTls.kubernetesContext(Path.of(properties.getKubernetesCaFile())))
|
||||
.connectTimeout(Duration.ofSeconds(5)).followRedirects(HttpClient.Redirect.NEVER).build());
|
||||
}
|
||||
|
||||
KubernetesHarnessDiscovery(ManagedAgentProperties.HarnessPool properties, ObjectMapper json, URI origin, HttpClient client) {
|
||||
KubernetesHarnessDiscovery(ManagedAgentSettings.HarnessPool properties, ObjectMapper json, URI origin, HttpClient client) {
|
||||
label(properties.getNamespace());
|
||||
label(properties.getServiceName());
|
||||
label(properties.getPortName());
|
||||
@@ -192,7 +192,7 @@ public final class KubernetesHarnessDiscovery implements HarnessDiscovery {
|
||||
}
|
||||
}
|
||||
|
||||
static List<Endpoint> endpoints(ManagedAgentProperties.HarnessPool properties, Iterable<JsonNode> slices) throws IOException {
|
||||
static List<Endpoint> endpoints(ManagedAgentSettings.HarnessPool properties, Iterable<JsonNode> slices) throws IOException {
|
||||
Map<String, Endpoint> result = new LinkedHashMap<>();
|
||||
for (JsonNode slice : slices) {
|
||||
JsonNode metadata = slice.path("metadata");
|
||||
+31
-25
@@ -17,19 +17,21 @@ import com.alibaba.qwen.code.daemon.PromptReceipt;
|
||||
import com.alibaba.qwen.code.daemon.SessionCreationOutcomeUnknownException;
|
||||
import com.alibaba.qwen.code.daemon.StreamHarnessEvents;
|
||||
import com.alibaba.qwen.code.daemon.SubmitHarnessTurn;
|
||||
import com.alibaba.qwen.code.managedagent.catalog.AgentDefinitionExtensions;
|
||||
import com.alibaba.qwen.code.managedagent.config.ManagedAgentProperties;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.catalog.AgentDefinitionExtensions;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.harness.HarnessConnector;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.harness.HostedHarnessRecoveryDeclinedException;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.security.WriterCredentialPolicy;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.session.AgentStateStore;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.workspace.WorkspaceToolProfiles;
|
||||
import com.alibaba.qwen.code.managedagent.model.StoreModels;
|
||||
import com.alibaba.qwen.code.managedagent.model.StoreModels.SessionRecord;
|
||||
import com.alibaba.qwen.code.managedagent.persistence.ManagedActionStore;
|
||||
import com.alibaba.qwen.code.managedagent.persistence.ManagedAgentDefinitionStore;
|
||||
import com.alibaba.qwen.code.managedagent.persistence.ManagedSessionSkills;
|
||||
import com.alibaba.qwen.code.managedagent.persistence.ManagedSessionStore;
|
||||
import com.alibaba.qwen.code.managedagent.persistence.SshWorkspaceStore;
|
||||
import com.alibaba.qwen.code.managedagent.persistence.WorkspaceExecutionStore;
|
||||
import com.alibaba.qwen.code.managedagent.security.WriterCredentialPolicy;
|
||||
import com.alibaba.qwen.code.managedagent.session.AgentStateStore;
|
||||
import com.alibaba.qwen.code.managedagent.workspace.WorkspaceToolProfiles;
|
||||
import com.alibaba.qwen.code.managedagent.repository.ManagedActionStore;
|
||||
import com.alibaba.qwen.code.managedagent.repository.ManagedAgentDefinitionStore;
|
||||
import com.alibaba.qwen.code.managedagent.repository.ManagedSessionSkills;
|
||||
import com.alibaba.qwen.code.managedagent.repository.ManagedSessionStore;
|
||||
import com.alibaba.qwen.code.managedagent.repository.SshWorkspaceStore;
|
||||
import com.alibaba.qwen.code.managedagent.repository.WorkspaceExecutionStore;
|
||||
import com.alibaba.qwen.code.managedagent.settings.ManagedAgentSettings;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -48,10 +50,10 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class QwenHostedHarnessConnector implements HarnessConnector {
|
||||
private final ManagedAgentProperties settings;
|
||||
private final ManagedAgentProperties.Harness properties;
|
||||
private final ManagedAgentProperties.SessionStore sessionStore;
|
||||
private final ManagedAgentProperties.ToolPublication toolPublication;
|
||||
private final ManagedAgentSettings settings;
|
||||
private final ManagedAgentSettings.Harness properties;
|
||||
private final ManagedAgentSettings.SessionStore sessionStore;
|
||||
private final ManagedAgentSettings.ToolPublication toolPublication;
|
||||
private final String workspaceId;
|
||||
private final AgentStateStore sessions;
|
||||
private final WorkspaceExecutionStore workspaceExecution;
|
||||
@@ -64,7 +66,7 @@ public class QwenHostedHarnessConnector implements HarnessConnector {
|
||||
@org.springframework.beans.factory.annotation.Autowired
|
||||
private com.fasterxml.jackson.databind.ObjectMapper extensionJson;
|
||||
@org.springframework.beans.factory.annotation.Autowired
|
||||
private com.alibaba.qwen.code.managedagent.persistence.ManagedExtensionRecordStore extensions;
|
||||
private com.alibaba.qwen.code.managedagent.repository.ManagedExtensionRecordStore extensions;
|
||||
private final WriterCredentialPolicy credentials;
|
||||
private final SshWorkspaceStore ssh;
|
||||
/** #13753 I2: this control plane serves child Workspaces (startup checks the shape). */
|
||||
@@ -88,12 +90,16 @@ public class QwenHostedHarnessConnector implements HarnessConnector {
|
||||
this.client = negotiated;
|
||||
}
|
||||
|
||||
public void configureExtensionResources(com.alibaba.qwen.code.managedagent.persistence.ManagedExtensionRecordStore records,
|
||||
public void configureExtensionResources(com.alibaba.qwen.code.managedagent.repository.ManagedExtensionRecordStore records,
|
||||
com.fasterxml.jackson.databind.ObjectMapper json) {
|
||||
this.extensions = records;
|
||||
this.extensionJson = json;
|
||||
}
|
||||
|
||||
public void configureSessionSkills(ManagedSessionSkills skills) {
|
||||
this.sessionSkills = skills;
|
||||
}
|
||||
|
||||
<T> T withPlacement(PoolCall call, Supplier<T> action) {
|
||||
PoolCall previous = poolCall.get();
|
||||
poolCall.set(call);
|
||||
@@ -150,27 +156,27 @@ public class QwenHostedHarnessConnector implements HarnessConnector {
|
||||
private final Map<AttachmentKey, AttachmentLock> attachmentLocks =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
public QwenHostedHarnessConnector(ManagedAgentProperties properties,
|
||||
public QwenHostedHarnessConnector(ManagedAgentSettings properties,
|
||||
AgentStateStore sessions, WorkspaceExecutionStore workspaceExecution) {
|
||||
this(properties, sessions, workspaceExecution, null);
|
||||
}
|
||||
|
||||
public QwenHostedHarnessConnector(
|
||||
ManagedAgentProperties properties,
|
||||
ManagedAgentSettings properties,
|
||||
AgentStateStore sessions,
|
||||
WorkspaceExecutionStore workspaceExecution,
|
||||
ManagedActionStore actions) {
|
||||
this(properties, sessions, workspaceExecution, actions, null, null);
|
||||
}
|
||||
|
||||
public QwenHostedHarnessConnector(ManagedAgentProperties properties,
|
||||
public QwenHostedHarnessConnector(ManagedAgentSettings properties,
|
||||
AgentStateStore sessions, WorkspaceExecutionStore workspaceExecution,
|
||||
ManagedActionStore actions, ManagedAgentDefinitionStore definitions,
|
||||
ManagedSessionStore resources) {
|
||||
this(properties, sessions, workspaceExecution, actions, definitions, resources, null);
|
||||
}
|
||||
|
||||
public QwenHostedHarnessConnector(ManagedAgentProperties properties,
|
||||
public QwenHostedHarnessConnector(ManagedAgentSettings properties,
|
||||
AgentStateStore sessions, WorkspaceExecutionStore workspaceExecution,
|
||||
ManagedActionStore actions, ManagedAgentDefinitionStore definitions,
|
||||
ManagedSessionStore resources, SshWorkspaceStore ssh) {
|
||||
@@ -589,7 +595,7 @@ public class QwenHostedHarnessConnector implements HarnessConnector {
|
||||
throw new IllegalStateException("Hosted Harness generation moved during passive load");
|
||||
}
|
||||
return new Attachment(attached.getHarnessBootId(),
|
||||
attached.getRuntimeRecovery(),
|
||||
HarnessSdkValues.recovery(attached.getRuntimeRecovery()),
|
||||
attached.getHarnessLastEventId(),
|
||||
attached.getHarnessEventEpoch());
|
||||
}
|
||||
@@ -815,7 +821,7 @@ public class QwenHostedHarnessConnector implements HarnessConnector {
|
||||
var preparedClient = client();
|
||||
if (!preparedClient.capabilities().getFeatures().contains("context-compression/1")) {
|
||||
throw new com.alibaba.qwen.code.managedagent.exception.ManagedAgentException(
|
||||
org.springframework.http.HttpStatus.CONFLICT, "unsupported_feature",
|
||||
409, "unsupported_feature",
|
||||
"Hosted context compression is unavailable.");
|
||||
}
|
||||
return () -> {
|
||||
@@ -1846,7 +1852,7 @@ public class QwenHostedHarnessConnector implements HarnessConnector {
|
||||
return attached;
|
||||
});
|
||||
return new Attachment(attached.getHarnessBootId(),
|
||||
pendingRecovery.contains(key) ? attached.getRuntimeRecovery()
|
||||
pendingRecovery.contains(key) ? HarnessSdkValues.recovery(attached.getRuntimeRecovery())
|
||||
: null,
|
||||
attached.getHarnessLastEventId(),
|
||||
attached.getHarnessEventEpoch());
|
||||
@@ -1932,7 +1938,7 @@ public class QwenHostedHarnessConnector implements HarnessConnector {
|
||||
// handed out again only until its continue or cancel has been
|
||||
// admitted; after that a re-entered Turn just resumes its stream.
|
||||
return new Attachment(cached.getHarnessBootId(),
|
||||
pendingRecovery.contains(key) ? cached.getRuntimeRecovery()
|
||||
pendingRecovery.contains(key) ? HarnessSdkValues.recovery(cached.getRuntimeRecovery())
|
||||
: null,
|
||||
cached.getHarnessLastEventId(),
|
||||
cached.getHarnessEventEpoch());
|
||||
+422
@@ -0,0 +1,422 @@
|
||||
package com.alibaba.qwen.code.managedagent.harness;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.contracts.harness.HarnessConnector;
|
||||
import com.alibaba.qwen.code.managedagent.model.StoreModels.OperationRecord;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** Converts SDK failures at the adapter boundary, including deferred stream reads. */
|
||||
public final class SdkHarnessBoundary implements HarnessConnector {
|
||||
private final HarnessConnector delegate;
|
||||
|
||||
public SdkHarnessBoundary(HarnessConnector delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return call(delegate::isAvailable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recoverForWake(String tenantId, String sessionId) {
|
||||
run(() -> delegate.recoverForWake(tenantId, sessionId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPublicChildAgents() {
|
||||
return call(delegate::supportsPublicChildAgents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String closeSession(OperationRecord operation) {
|
||||
return call(() -> delegate.closeSession(operation));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runLifecycleChildOperation(OperationRecord operation, Map<String, Object> body) {
|
||||
run(() -> delegate.runLifecycleChildOperation(operation, body));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> drainInstance(String instanceId, String operationId) {
|
||||
return call(() -> delegate.drainInstance(instanceId, operationId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> releaseOwnership(String tenantId, String sessionId,
|
||||
String operationId, long expectedPlacementEpoch) {
|
||||
return call(() -> delegate.releaseOwnership(tenantId, sessionId, operationId, expectedPlacementEpoch));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsApprovalSelection() {
|
||||
return call(delegate::supportsApprovalSelection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsLiveApprovalSelection() {
|
||||
return call(delegate::supportsLiveApprovalSelection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAutoApproval() {
|
||||
return call(delegate::supportsAutoApproval);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPermissionRules() {
|
||||
return call(delegate::supportsPermissionRules);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAgentDefinitionApprovalSelection() {
|
||||
return call(delegate::supportsAgentDefinitionApprovalSelection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsModelSelection() {
|
||||
return call(delegate::supportsModelSelection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsUserMessageQueue() {
|
||||
return call(delegate::supportsUserMessageQueue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsContextCompression() {
|
||||
return call(delegate::supportsContextCompression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContextCompressionSubmission prepareContextCompression(String tenantId, String sessionId,
|
||||
Map<String, Object> body) {
|
||||
return call(() -> {
|
||||
var prepared = delegate.prepareContextCompression(tenantId, sessionId, body);
|
||||
return () -> call(prepared::submit);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> runContextCompression(String tenantId, String sessionId,
|
||||
Map<String, Object> body) {
|
||||
return call(() -> delegate.runContextCompression(tenantId, sessionId, body));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> readContextCompression(String tenantId, String sessionId,
|
||||
String operationId) {
|
||||
return call(() -> delegate.readContextCompression(tenantId, sessionId, operationId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPlanMode() {
|
||||
return call(delegate::supportsPlanMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInputAttachments() {
|
||||
return call(delegate::supportsInputAttachments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSessionSkills() {
|
||||
return call(delegate::supportsSessionSkills);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsMcpManagement() {
|
||||
return call(delegate::supportsMcpManagement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> mcpManagement(String tenantId, String sessionId) {
|
||||
return call(() -> delegate.mcpManagement(tenantId, sessionId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> manageMcp(String tenantId, String sessionId, Map<String, Object> command) {
|
||||
return call(() -> delegate.manageMcp(tenantId, sessionId, command));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> mcpManagementOperation(String tenantId, String sessionId, String operationId) {
|
||||
return call(() -> delegate.mcpManagementOperation(tenantId, sessionId, operationId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSessionExtensions() {
|
||||
return call(delegate::supportsSessionExtensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> applyExtensions(String tenantId, String sessionId, Map<String, Object> command) {
|
||||
return call(() -> delegate.applyExtensions(tenantId, sessionId, command));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> continueExtensionOperation(String tenantId, String sessionId, String operationId, boolean recoverPrevious) {
|
||||
return call(() -> delegate.continueExtensionOperation(tenantId, sessionId, operationId, recoverPrevious));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWorkspaceFilesAvailable() {
|
||||
return call(delegate::isWorkspaceFilesAvailable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsLifecycle() {
|
||||
return call(delegate::supportsLifecycle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsLifecycle(String profile) {
|
||||
return call(() -> delegate.supportsLifecycle(profile));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareLifecycleResources(OperationRecord operation) {
|
||||
run(() -> delegate.prepareLifecycleResources(operation));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settleLifecycleResources(OperationRecord operation) {
|
||||
run(() -> delegate.settleLifecycleResources(operation));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFencedTitles() {
|
||||
return call(delegate::supportsFencedTitles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode settleLifecycle(OperationRecord operation) {
|
||||
return call(() -> delegate.settleLifecycle(operation));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detachLifecycle(OperationRecord operation) {
|
||||
run(() -> delegate.detachLifecycle(operation));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment createOrLoad(String tenantId, String sessionId,
|
||||
boolean loadExisting) {
|
||||
return call(() -> delegate.createOrLoad(tenantId, sessionId, loadExisting));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment createOrLoad(String tenantId, String sessionId,
|
||||
boolean loadExisting, boolean passiveManagedRuntimeRecovery) {
|
||||
return call(() -> delegate.createOrLoad(tenantId, sessionId, loadExisting, passiveManagedRuntimeRecovery));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment recoverManagedRuntime(String tenantId, String sessionId,
|
||||
boolean cancellation) {
|
||||
return call(() -> delegate.recoverManagedRuntime(tenantId, sessionId, cancellation));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment recoverManagedRuntime(String tenantId, String sessionId,
|
||||
boolean cancellation, String promptId) {
|
||||
return call(() -> delegate.recoverManagedRuntime(tenantId, sessionId, cancellation, promptId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment recoverManagedCancellation(String tenantId,
|
||||
String sessionId) {
|
||||
return call(() -> delegate.recoverManagedCancellation(tenantId, sessionId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment recoverManagedCancellation(String tenantId,
|
||||
String sessionId, String promptId) {
|
||||
return call(() -> delegate.recoverManagedCancellation(tenantId, sessionId, promptId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment recoverManagedFailureCancellation(String tenantId,
|
||||
String sessionId, String turnId, String dispatchOwner) {
|
||||
return call(() -> delegate.recoverManagedFailureCancellation(tenantId, sessionId, turnId, dispatchOwner));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment recoverManagedFailureCancellation(String tenantId,
|
||||
String sessionId, String turnId, String dispatchOwner, String promptId) {
|
||||
return call(() -> delegate.recoverManagedFailureCancellation(tenantId, sessionId, turnId, dispatchOwner, promptId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void discardManagedRecovery(String tenantId, String sessionId) {
|
||||
run(() -> delegate.discardManagedRecovery(tenantId, sessionId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecoveryStatus inspectRecovery(String tenantId, String sessionId, String promptId) {
|
||||
return call(() -> delegate.inspectRecovery(tenantId, sessionId, promptId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment cachedRecoveryAttachment(String tenantId, String sessionId) {
|
||||
return call(() -> delegate.cachedRecoveryAttachment(tenantId, sessionId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean recheckRecoveryCleanup(String tenantId, String sessionId, String promptId) {
|
||||
return call(() -> delegate.recheckRecoveryCleanup(tenantId, sessionId, promptId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Admission submit(String tenantId, String sessionId, String promptId,
|
||||
List<Map<String, Object>> input, String payloadDigest) {
|
||||
return call(() -> delegate.submit(tenantId, sessionId, promptId, input, payloadDigest));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Admission continueManagedRuntime(String tenantId,
|
||||
String sessionId, String promptId, String checkpointId,
|
||||
String activationId) {
|
||||
return call(() -> delegate.continueManagedRuntime(tenantId, sessionId, promptId, checkpointId, activationId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Admission cancelManagedRuntime(String tenantId, String sessionId,
|
||||
String promptId, String checkpointId, String activationId) {
|
||||
return call(() -> delegate.cancelManagedRuntime(tenantId, sessionId, promptId, checkpointId, activationId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceStream stream(String tenantId, String sessionId, long lastEventId,
|
||||
String eventEpoch) {
|
||||
return call(() -> streamBoundary(delegate.stream(tenantId, sessionId, lastEventId, eventEpoch)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceStream observeStream(String tenantId, String sessionId, String bootId,
|
||||
long lastEventId, String eventEpoch) {
|
||||
return call(() -> streamBoundary(delegate.observeStream(tenantId, sessionId, bootId, lastEventId, eventEpoch)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveAction(
|
||||
String tenantId,
|
||||
String sessionId,
|
||||
String actionId,
|
||||
JsonNode response) {
|
||||
run(() -> delegate.resolveAction(tenantId, sessionId, actionId, response));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel(String tenantId, String sessionId) {
|
||||
run(() -> delegate.cancel(tenantId, sessionId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelOriginalTurn(String tenantId, String sessionId, String promptId) {
|
||||
run(() -> delegate.cancelOriginalTurn(tenantId, sessionId, promptId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> runAutomationOperation(String tenantId,
|
||||
String sessionId, Map<String, Object> body) {
|
||||
return call(() -> delegate.runAutomationOperation(tenantId, sessionId, body));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runChildOperation(String tenantId, String sessionId,
|
||||
Map<String, Object> body) {
|
||||
run(() -> delegate.runChildOperation(tenantId, sessionId, body));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> runMessageOperation(String tenantId,
|
||||
String sessionId, Map<String, Object> body) {
|
||||
return call(() -> delegate.runMessageOperation(tenantId, sessionId, body));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> runChannelOperation(String tenantId,
|
||||
String sessionId, Map<String, Object> body) {
|
||||
return call(() -> delegate.runChannelOperation(tenantId, sessionId, body));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rename(String tenantId, String sessionId, String title) {
|
||||
run(() -> delegate.rename(tenantId, sessionId, title));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rename(String tenantId, String sessionId, String title, long revision) {
|
||||
run(() -> delegate.rename(tenantId, sessionId, title, revision));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String closeSession(String tenantId, String sessionId) {
|
||||
return call(() -> delegate.closeSession(tenantId, sessionId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AttachedSession> attachedSessions() {
|
||||
return call(delegate::attachedSessions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String writerIdentity() {
|
||||
return call(delegate::writerIdentity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean activateForWake(String tenantId, String sessionId) {
|
||||
return call(() -> delegate.activateForWake(tenantId, sessionId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
run(delegate::close);
|
||||
}
|
||||
|
||||
private static <T> T call(Supplier<T> operation) {
|
||||
try {
|
||||
return operation.get();
|
||||
} catch (RuntimeException error) {
|
||||
throw HarnessSdkValues.runtime(error);
|
||||
}
|
||||
}
|
||||
|
||||
private static void run(Runnable operation) {
|
||||
try {
|
||||
operation.run();
|
||||
} catch (RuntimeException error) {
|
||||
throw HarnessSdkValues.runtime(error);
|
||||
}
|
||||
}
|
||||
|
||||
private static SourceStream streamBoundary(SourceStream stream) {
|
||||
return new SourceStream() {
|
||||
@Override
|
||||
public String eventEpoch() {
|
||||
return call(stream::eventEpoch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceEvent next() {
|
||||
return call(stream::next);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean recoveryBlocked(long afterEventId) {
|
||||
return call(() -> stream.recoveryBlocked(afterEventId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
run(stream::close);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.alibaba.qwen.code.managedagent.harness;
|
||||
|
||||
import com.alibaba.qwen.code.daemon.HostedPermissionRules;
|
||||
import com.alibaba.qwen.code.daemon.SubmitHarnessTurn;
|
||||
import com.alibaba.qwen.code.managedagent.harness.contracts.HarnessRules;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class SdkHarnessRules implements HarnessRules {
|
||||
@Override
|
||||
public String payloadDigest(List<Map<String, Object>> input) {
|
||||
return SubmitHarnessTurn.computePayloadDigest(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> permissionRules(Map<?, ?> rules) {
|
||||
return HostedPermissionRules.copyOf(rules);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> nonEmptyPermissionRules(Map<String, List<String>> rules) {
|
||||
return HostedPermissionRules.nonEmpty(rules);
|
||||
}
|
||||
}
|
||||
+3
@@ -1,5 +1,8 @@
|
||||
package com.alibaba.qwen.code.managedagent.harness;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.contracts.harness.HarnessConnector;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.harness.HarnessDisabledException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
+15
-11
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.qwen.code.runtimebroker.transport;
|
||||
package com.alibaba.qwen.code.runtimebroker.adapter;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONReader;
|
||||
@@ -18,11 +18,21 @@ import com.alibaba.qwen.code.runtimebroker.RuntimePublicationGrant;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeScope;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeSession;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeSessionRecord;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeTransport;
|
||||
import com.alibaba.qwen.code.runtimebroker.WorkspaceExecutionProfile;
|
||||
import com.alibaba.qwen.code.runtimebroker.managedworkspace.ContextBinding;
|
||||
import com.alibaba.qwen.code.runtimebroker.provisioning.ManagedContextProtocol;
|
||||
import com.alibaba.qwen.code.runtimebroker.provisioning.ProviderRuntimeProtocol;
|
||||
import com.alibaba.qwen.code.runtimebroker.protocol.ManagedContextProtocol;
|
||||
import com.alibaba.qwen.code.runtimebroker.protocol.ProviderRuntimeProtocol;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.CsiFileFinalizeProtocol;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.CsiFileHistoryProtocol;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.CsiNativeReadbackProtocol;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.ManagedCsiFilesProtocol;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.ManagedCsiProtocol;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.ManagedExtensionCommandProtocol;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.ManagedExtensionProtocol;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.ManagedHookProtocol;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.ManagedMcpProtocol;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.ManagedMonitorProtocol;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.ManagedShellProtocol;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.math.BigDecimal;
|
||||
@@ -54,7 +64,7 @@ import java.util.function.Function;
|
||||
* their separate Session control protocol. MCP controls have their own
|
||||
* bounded envelope.
|
||||
*/
|
||||
public final class HttpRuntimeTransport implements RuntimeTransport {
|
||||
public final class HttpRuntimeTransport implements com.alibaba.qwen.code.runtimebroker.WorkspacePhysicalTransport {
|
||||
static final int BODY_LIMIT_BYTES = 16 * 1024;
|
||||
static final int TOOL_REQUEST_LIMIT_BYTES = 256 * 1024;
|
||||
static final int TOOL_RESULT_LIMIT_BYTES = 1024 * 1024;
|
||||
@@ -126,12 +136,6 @@ public final class HttpRuntimeTransport implements RuntimeTransport {
|
||||
managedExtensionCacheDirectory, managedExtensionCgroupRoot) : null);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ExtensionPreparation {
|
||||
CompletionStage<Map<String, Object>> prepare(RuntimeBindingRecord runtime, String harnessSessionId,
|
||||
Map<String, Object> operation);
|
||||
}
|
||||
|
||||
public void configureExtensionPreparation(ExtensionPreparation preparation) {
|
||||
this.extensionPreparation = java.util.Objects.requireNonNull(preparation, "preparation");
|
||||
}
|
||||
+3
-1
@@ -1,8 +1,10 @@
|
||||
package com.alibaba.qwen.code.runtimebroker.security;
|
||||
package com.alibaba.qwen.code.runtimebroker.adapter.security;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONWriter;
|
||||
import com.alibaba.qwen.code.runtimebroker.BrokerValues;
|
||||
import com.alibaba.qwen.code.runtimebroker.security.ProtectedSecret;
|
||||
import com.alibaba.qwen.code.runtimebroker.security.SecretProtector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
+2
-1
@@ -2,7 +2,8 @@ package com.alibaba.qwen.code.runtimebroker.provisioning;
|
||||
|
||||
import com.alibaba.qwen.code.runtimebroker.JsonCodec;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeBrokerException;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.HttpRuntimeTransport;
|
||||
import com.alibaba.qwen.code.runtimebroker.adapter.HttpRuntimeTransport;
|
||||
import com.alibaba.qwen.code.runtimebroker.spi.ExternalRuntimeClient;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
+2
-2
@@ -11,8 +11,8 @@ import com.alibaba.qwen.code.runtimebroker.RuntimeProvisionSeed;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeProvisioner;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeResourceHandle;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeScope;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.HttpRuntimeTransport;
|
||||
|
||||
import com.alibaba.qwen.code.runtimebroker.adapter.HttpRuntimeTransport;
|
||||
import com.alibaba.qwen.code.runtimebroker.spi.ExternalRuntimeClient;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
|
||||
import java.net.URI;
|
||||
+3
@@ -7,6 +7,9 @@ import com.alibaba.qwen.code.runtimebroker.RuntimeProvisionSeed;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeResourceHandle;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeScope;
|
||||
import com.alibaba.qwen.code.runtimebroker.WorkspaceExecutionProfile;
|
||||
import com.alibaba.qwen.code.runtimebroker.protocol.ManagedContextProtocol;
|
||||
import com.alibaba.qwen.code.runtimebroker.spi.ExternalRuntimeClient;
|
||||
import com.alibaba.qwen.code.runtimebroker.spi.ExternalStorageRegistry;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashMap;
|
||||
+3
-2
@@ -2,6 +2,7 @@ package com.alibaba.qwen.code.runtimebroker.provisioning;
|
||||
|
||||
import com.alibaba.qwen.code.runtimebroker.BrokerValues;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeBrokerException;
|
||||
import com.alibaba.qwen.code.runtimebroker.spi.KubernetesRuntimeClient;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
@@ -23,8 +24,8 @@ public final class KubernetesCsiLogReader {
|
||||
public record Source(String podName, String podUid, String nodeName, String nodeUid,
|
||||
String daemonSetUid, String containerId, String imageId) {
|
||||
public Source {
|
||||
KubernetesHttpRuntimeClient.dnsSubdomain(podName);
|
||||
KubernetesHttpRuntimeClient.dnsSubdomain(nodeName);
|
||||
com.alibaba.qwen.code.runtimebroker.protocol.KubernetesNames.dnsSubdomain(podName);
|
||||
com.alibaba.qwen.code.runtimebroker.protocol.KubernetesNames.dnsSubdomain(nodeName);
|
||||
if (!podName.startsWith("csi-plugin-") || !uuid(podUid) || !uuid(nodeUid) || !uuid(daemonSetUid)
|
||||
|| containerId == null || !containerId.matches("containerd://[0-9a-f]{64}")
|
||||
|| imageId == null || imageId.length() > 512 || !imageId.matches("[^\\s]*sha256:[0-9a-f]{64}")) {
|
||||
+20
-6
@@ -4,7 +4,7 @@ import com.alibaba.qwen.code.runtimebroker.BrokerValues;
|
||||
import com.alibaba.qwen.code.runtimebroker.JsonCodec;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeBrokerException;
|
||||
import com.alibaba.qwen.code.runtimebroker.WorkspaceCsiRuntimeIdentity;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.HttpRuntimeTransport;
|
||||
import com.alibaba.qwen.code.runtimebroker.adapter.HttpRuntimeTransport;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
@@ -24,17 +24,17 @@ import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** Bounded core-v1 API calls with rotating service-account tokens and explicit CA trust. */
|
||||
public final class KubernetesHttpRuntimeClient implements KubernetesRuntimeClient {
|
||||
public final class KubernetesHttpRuntimeClient implements com.alibaba.qwen.code.runtimebroker.spi.KubernetesMaintenanceClient {
|
||||
private static final int RESPONSE_LIMIT = 1024 * 1024;
|
||||
private static final int CONFIG_MAP_RESPONSE_LIMIT = RESPONSE_LIMIT + 64 * 1024;
|
||||
private static final int DELETE_REQUEST_LIMIT = 16 * 1024;
|
||||
@@ -188,8 +188,6 @@ public final class KubernetesHttpRuntimeClient implements KubernetesRuntimeClien
|
||||
return result;
|
||||
}
|
||||
|
||||
public record DeleteResponse(int status, byte[] body, boolean complete) { }
|
||||
|
||||
@Override
|
||||
public CompletionStage<String> readCsiPodLog(String podName) {
|
||||
dnsSubdomain(podName);
|
||||
@@ -335,4 +333,20 @@ public final class KubernetesHttpRuntimeClient implements KubernetesRuntimeClien
|
||||
return new RuntimeBrokerException(status >= 400 && status <= 599 ? status : 502,
|
||||
"runtime_kubernetes_api_failed", "Kubernetes API request failed.", retryable);
|
||||
}
|
||||
public java.util.concurrent.CompletionStage<Void> verifyProtection(
|
||||
com.alibaba.qwen.code.managedagent.contracts.persistence.WorkspaceCsiRegistration registration,
|
||||
com.alibaba.qwen.code.managedagent.contracts.runtime.ProtectionIdentity protection) {
|
||||
return new com.alibaba.qwen.code.managedagent.adapters.runtime.WorkspaceCsiResourceGuard(
|
||||
this, registration.clusterDomain(), registration, protection).verify().thenApply(receipt -> null);
|
||||
}
|
||||
|
||||
public com.alibaba.qwen.code.managedagent.contracts.workspace.ResidentReclamation.Baseline reclamationBaseline(
|
||||
com.alibaba.qwen.code.runtimebroker.RuntimeBindingRecord original,
|
||||
com.alibaba.qwen.code.managedagent.settings.ManagedAgentSettings.RuntimeBroker.ResidentWorkspace selected,
|
||||
String finalizer, java.time.Instant now) { return com.alibaba.qwen.code.managedagent.adapters.runtime.ResidentWorkspaceReclamation.prepare(original, selected, finalizer, this, now); }
|
||||
public com.alibaba.qwen.code.managedagent.contracts.workspace.ResidentReclamation.Receipt reclamationReceipt(
|
||||
com.alibaba.qwen.code.runtimebroker.RuntimeBindingRecord original,
|
||||
com.alibaba.qwen.code.managedagent.settings.ManagedAgentSettings.RuntimeBroker.ResidentWorkspace selected,
|
||||
com.alibaba.qwen.code.managedagent.contracts.workspace.ResidentReclamation.Baseline baseline,
|
||||
Map<String, Object> pod, java.time.Instant now) { return com.alibaba.qwen.code.managedagent.adapters.runtime.ResidentWorkspaceReclamation.complete(original, selected, baseline, pod, this, now); }
|
||||
}
|
||||
+3
-2
@@ -11,7 +11,8 @@ import com.alibaba.qwen.code.runtimebroker.RuntimeProvisionSeed;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeProvisioner;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeResourceHandle;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeScope;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.HttpRuntimeTransport;
|
||||
import com.alibaba.qwen.code.runtimebroker.adapter.HttpRuntimeTransport;
|
||||
import com.alibaba.qwen.code.runtimebroker.spi.KubernetesRuntimeClient;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
@@ -65,7 +66,7 @@ public final class KubernetesRuntimeProvisioner implements RuntimeProvisioner {
|
||||
|| startupTimeout == null || startupTimeout.isNegative() || startupTimeout.isZero()) {
|
||||
throw new IllegalArgumentException("Client, pinned image, worker command and positive timeout are required");
|
||||
}
|
||||
KubernetesHttpRuntimeClient.dnsLabel(namespace);
|
||||
com.alibaba.qwen.code.runtimebroker.protocol.KubernetesNames.dnsLabel(namespace);
|
||||
this.client = client;
|
||||
this.cluster = BrokerValues.requireId(cluster, "cluster");
|
||||
this.namespace = namespace;
|
||||
+3
-2
@@ -17,7 +17,8 @@ import com.alibaba.qwen.code.runtimebroker.RuntimeRecoveryEvidence;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeResourceHandle;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeScope;
|
||||
import com.alibaba.qwen.code.runtimebroker.WorkspaceExecutionProfile;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.HttpRuntimeTransport;
|
||||
import com.alibaba.qwen.code.runtimebroker.adapter.HttpRuntimeTransport;
|
||||
import com.alibaba.qwen.code.runtimebroker.protocol.ManagedContextProtocol;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@@ -587,7 +588,7 @@ public final class LocalProcessRuntimeProvisioner
|
||||
// A closed provisioner is not a managed-context startup
|
||||
// failure, and recovery is not blocked by it: keep the
|
||||
// refusal's own message and retryable flag for callers that
|
||||
// provision directly. Through RuntimeBrokerService a
|
||||
// provision directly. Through RuntimeBrokerOperations a
|
||||
// managed-context provision failure blocks recovery either
|
||||
// way, so this changes the surfaced error, not that outcome.
|
||||
throw closedRefusal;
|
||||
+3
-3
@@ -229,7 +229,7 @@ final class LocalRuntimeStore {
|
||||
}
|
||||
|
||||
static boolean supported(RuntimeResourceHandle handle) {
|
||||
return handle != null && LocalProcessRuntimeProvisioner.KIND.equals(handle.getKind())
|
||||
return handle != null && com.alibaba.qwen.code.runtimebroker.protocol.RuntimeProviderKinds.LOCAL_PROCESS.equals(handle.getKind())
|
||||
&& handle.getVersion() == 2;
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ final class LocalRuntimeStore {
|
||||
|| !Objects.equals(value.get("version"), 1)) {
|
||||
throw blocked();
|
||||
}
|
||||
return new Registration(RuntimeResourceHandle.fromJson(LocalProcessRuntimeProvisioner.KIND, 2,
|
||||
return new Registration(RuntimeResourceHandle.fromJson(com.alibaba.qwen.code.runtimebroker.protocol.RuntimeProviderKinds.LOCAL_PROCESS, 2,
|
||||
(String) value.get("handle")), State.valueOf((String) value.get("state")),
|
||||
((Number) value.get("pid")).longValue(),
|
||||
"".equals(value.get("started")) ? null : (String) value.get("started"),
|
||||
@@ -440,7 +440,7 @@ final class LocalRuntimeStore {
|
||||
scope.getTenantId(), scope.getWorkspaceId(), scope.getWorkspaceGeneration(), scope.getCanonicalCwd(),
|
||||
scope.getCapabilityDigest(), scope.getIsolationClass(), request.getProvisionerKind(),
|
||||
Objects.toString(request.getIsolationKey(), ""), Objects.toString(request.getStorageId(), "")))));
|
||||
return new RuntimeResourceHandle(LocalProcessRuntimeProvisioner.KIND, 2, values);
|
||||
return new RuntimeResourceHandle(com.alibaba.qwen.code.runtimebroker.protocol.RuntimeProviderKinds.LOCAL_PROCESS, 2, values);
|
||||
}
|
||||
|
||||
private static String digest(byte[] bytes) {
|
||||
File renamed without changes.
+120
@@ -0,0 +1,120 @@
|
||||
package com.alibaba.qwen.code.runtimebroker.provisioning;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.contracts.workspace.StorageIdentityReader;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.workspace.StorageIdentityReader.Identity;
|
||||
import org.apache.commons.codec.digest.HmacAlgorithms;
|
||||
import org.apache.commons.codec.digest.HmacUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.time.DateTimeException;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.ResolverStyle;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
public final class NativeStorageIdentityReader implements StorageIdentityReader {
|
||||
private static final DateTimeFormatter LINUX_BIRTH_TIME_FORMAT = DateTimeFormatter
|
||||
.ofPattern("uuuu-MM-dd HH:mm:ss.SSSSSSSSS xx", Locale.ROOT)
|
||||
.withResolverStyle(ResolverStyle.STRICT);
|
||||
public Identity read(Path root) throws IOException {
|
||||
String os = System.getProperty("os.name");
|
||||
if (!List.of("Linux", "Mac OS X").contains(os)) {
|
||||
throw new IOException("Unsupported mount identity provider");
|
||||
}
|
||||
Map<String, Object> attributes = Files.readAttributes(root,
|
||||
"unix:isDirectory,dev,ino,creationTime,lastModifiedTime", LinkOption.NOFOLLOW_LINKS);
|
||||
if (!Boolean.TRUE.equals(attributes.get("isDirectory")) || !root.isAbsolute()
|
||||
|| !root.equals(root.toRealPath())) {
|
||||
throw new IOException("Workspace root is not canonical");
|
||||
}
|
||||
String device = Long.toUnsignedString(((Number) attributes.get("dev")).longValue());
|
||||
String inode = Long.toUnsignedString(((Number) attributes.get("ino")).longValue());
|
||||
String birthTime = "Mac OS X".equals(os) ? MacOsHostIdentity.birthTime(root, device, inode)
|
||||
: linuxBirthTime(root, device, inode, (FileTime) attributes.get("creationTime"),
|
||||
(FileTime) attributes.get("lastModifiedTime"));
|
||||
String machineId = "Mac OS X".equals(os) ? MacOsHostIdentity.machineId()
|
||||
: Files.readString(Path.of("/etc/machine-id")).strip();
|
||||
if (machineId.isEmpty() || machineId.length() > 256) {
|
||||
throw new IOException("Host identity is unavailable");
|
||||
}
|
||||
String host;
|
||||
try {
|
||||
host = new HmacUtils(HmacAlgorithms.HMAC_SHA_256,
|
||||
machineId.getBytes(StandardCharsets.UTF_8)).hmacHex(
|
||||
"Qwen-Code/verified-workspace/v2".getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IllegalArgumentException error) {
|
||||
if (error.getCause() instanceof GeneralSecurityException cause) {
|
||||
throw new IOException("Host identity is unavailable", cause);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return new Identity(root.toString(), host, device, inode, birthTime);
|
||||
}
|
||||
|
||||
private static String linuxBirthTime(Path root, String device, String inode,
|
||||
FileTime creation, FileTime modified) throws IOException {
|
||||
if (!creation.equals(modified) || creation.compareTo(FileTime.fromMillis(0)) <= 0) {
|
||||
return verifiedBirthTime(creation, modified);
|
||||
}
|
||||
// A marker write can share the birth clock tick. Confirm the exact root,
|
||||
// rather than treating the JDK's possible mtime fallback as birth evidence.
|
||||
ProcessBuilder builder = new ProcessBuilder("/usr/bin/stat", "-c", "%d|%i|%w", "--", root.toString())
|
||||
.redirectError(ProcessBuilder.Redirect.DISCARD);
|
||||
builder.environment().put("LC_ALL", "C");
|
||||
builder.environment().put("TZ", "UTC");
|
||||
Process process = builder.start();
|
||||
try {
|
||||
process.getOutputStream().close();
|
||||
if (!process.waitFor(5, TimeUnit.SECONDS) || process.exitValue() != 0) {
|
||||
throw new IOException("Workspace birth time probe failed");
|
||||
}
|
||||
byte[] output = process.getInputStream().readNBytes(257);
|
||||
if (output.length > 256) {
|
||||
throw new IOException("Workspace birth time probe exceeds its size limit");
|
||||
}
|
||||
return verifiedNativeBirthTime(new String(output, StandardCharsets.UTF_8).strip(),
|
||||
device, inode, creation);
|
||||
} catch (InterruptedException error) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Workspace birth time probe interrupted", error);
|
||||
} finally {
|
||||
process.destroyForcibly();
|
||||
process.getInputStream().close();
|
||||
process.getErrorStream().close();
|
||||
}
|
||||
}
|
||||
|
||||
public static String verifiedNativeBirthTime(String output, String device, String inode,
|
||||
FileTime creation) throws IOException {
|
||||
String[] fields = output.split("\\|", -1);
|
||||
if (fields.length != 3 || !device.equals(fields[0]) || !inode.equals(fields[1])) {
|
||||
throw new IOException("Workspace storage identity changed or is unavailable");
|
||||
}
|
||||
try {
|
||||
Instant birth = OffsetDateTime.parse(fields[2], LINUX_BIRTH_TIME_FORMAT).toInstant();
|
||||
if (!birth.isAfter(Instant.EPOCH) || !birth.equals(creation.toInstant())) {
|
||||
throw new IOException("Workspace birth time is unavailable or ambiguous");
|
||||
}
|
||||
return creation.toInstant().toString();
|
||||
} catch (DateTimeException error) {
|
||||
throw new IOException("Workspace native birth time is invalid", error);
|
||||
}
|
||||
}
|
||||
|
||||
public static String verifiedBirthTime(FileTime creation, FileTime modified) throws IOException {
|
||||
// OpenJDK 21 substitutes mtime or epoch when Linux does not provide birth time.
|
||||
if (creation.compareTo(FileTime.fromMillis(0)) <= 0 || creation.equals(modified)) {
|
||||
throw new IOException("Workspace birth time is unavailable or ambiguous");
|
||||
}
|
||||
return creation.toInstant().toString();
|
||||
}
|
||||
}
|
||||
File renamed without changes.
+2
-1
@@ -4,6 +4,7 @@ import com.alibaba.qwen.code.runtimebroker.BrokerValues;
|
||||
import com.alibaba.qwen.code.runtimebroker.JsonCodec;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeBrokerException;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeLease;
|
||||
import com.alibaba.qwen.code.runtimebroker.spi.SshRuntimeNode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
@@ -38,7 +39,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/** Owns SSH control sockets and ephemeral forwarding addresses, never remote workers. */
|
||||
public final class SshConnectionManager implements AutoCloseable {
|
||||
public final class SshConnectionManager implements com.alibaba.qwen.code.runtimebroker.spi.SshConnections {
|
||||
private static final int RECORD_LIMIT = 64 * 1024;
|
||||
private static final Duration COMMAND_TIMEOUT = Duration.ofSeconds(60);
|
||||
private final URI publicationEndpoint;
|
||||
+7
-4
@@ -12,7 +12,11 @@ import com.alibaba.qwen.code.runtimebroker.RuntimeProvisioner;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeResourceHandle;
|
||||
import com.alibaba.qwen.code.runtimebroker.RuntimeScope;
|
||||
import com.alibaba.qwen.code.runtimebroker.WorkspaceExecutionProfile;
|
||||
import com.alibaba.qwen.code.runtimebroker.transport.HttpRuntimeTransport;
|
||||
import com.alibaba.qwen.code.runtimebroker.adapter.HttpRuntimeTransport;
|
||||
import com.alibaba.qwen.code.runtimebroker.protocol.ManagedContextProtocol;
|
||||
import com.alibaba.qwen.code.runtimebroker.spi.SshConnections;
|
||||
import com.alibaba.qwen.code.runtimebroker.spi.SshRuntimeNode;
|
||||
import com.alibaba.qwen.code.runtimebroker.spi.SshRuntimeStorage;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -43,7 +47,7 @@ public final class SshRuntimeProvisioner implements RuntimeProvisioner {
|
||||
"provisionRequestId", "runtimeInstanceId", "runtimeIncarnation", "leaseId", "epoch", "retirementId", "retiredAt");
|
||||
private final Function<String, SshRuntimeNode> nodes;
|
||||
private final Function<RuntimeScope, SshRuntimeStorage> storages;
|
||||
private final SshConnectionManager connections;
|
||||
private final SshConnections connections;
|
||||
private final HttpRuntimeTransport transport;
|
||||
private final ExecutorService executor = new ThreadPoolExecutor(4, 4, 0, TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<>(64), Thread.ofPlatform().daemon().name("runtime-ssh-provisioner-", 0).factory());
|
||||
@@ -53,7 +57,7 @@ public final class SshRuntimeProvisioner implements RuntimeProvisioner {
|
||||
private String managedExtensionCgroupRoot;
|
||||
|
||||
public SshRuntimeProvisioner(Function<String, SshRuntimeNode> nodes,
|
||||
Function<RuntimeScope, SshRuntimeStorage> storages, SshConnectionManager connections,
|
||||
Function<RuntimeScope, SshRuntimeStorage> storages, SshConnections connections,
|
||||
HttpRuntimeTransport transport) {
|
||||
this.nodes = java.util.Objects.requireNonNull(nodes, "nodes");
|
||||
this.storages = java.util.Objects.requireNonNull(storages, "storages");
|
||||
@@ -257,7 +261,6 @@ public final class SshRuntimeProvisioner implements RuntimeProvisioner {
|
||||
// Let queued control futures run their closed checks instead of abandoning them.
|
||||
executor.shutdown();
|
||||
owned.clear();
|
||||
connections.close();
|
||||
}
|
||||
|
||||
private <T> CompletionStage<T> submit(Supplier<T> operation) {
|
||||
File renamed without changes.
+4
-2
@@ -9,10 +9,10 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** Bounded Workspace file exchanges using the configured local CLI. */
|
||||
public final class WorkspaceFileProcess {
|
||||
public final class WorkspaceFileProcess implements com.alibaba.qwen.code.managedagent.contracts.workspace.LocalWorkspaceFiles {
|
||||
public static final int MAX_UPLOAD_BYTES = 8 * 1024 * 1024;
|
||||
public static final int MAX_UPLOAD_REQUEST_BYTES = 12 * 1024 * 1024;
|
||||
private WorkspaceFileProcess() {
|
||||
public WorkspaceFileProcess() {
|
||||
}
|
||||
|
||||
public static Map<String, Object> read(String node, String entry, Map<String, Object> request) {
|
||||
@@ -86,4 +86,6 @@ public final class WorkspaceFileProcess {
|
||||
throw unsupported();
|
||||
}
|
||||
}
|
||||
public Map<String, Object> readFiles(String node, String entry, Map<String, Object> request) { return read(node, entry, request); }
|
||||
public Map<String, Object> uploadFiles(String node, String entry, Map<String, Object> request) { return upload(node, entry, request); }
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>qwen-managed-agent-parent</artifactId>
|
||||
<version>0.1.0-alpha</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>qwen-managed-agent-contracts</artifactId>
|
||||
<name>Qwen Managed Agent Contracts</name>
|
||||
<properties>
|
||||
<managed.module.root>${project.basedir}/..</managed.module.root>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.networknt</groupId>
|
||||
<artifactId>json-schema-validator</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>${managed.module.root}/../core/src/managed-runtime/contracts</directory>
|
||||
<includes>
|
||||
<include>managed-tool-result-v1.schema.json</include>
|
||||
</includes>
|
||||
<targetPath>contracts</targetPath>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.github.spotbugs</groupId>
|
||||
<artifactId>spotbugs-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<failIfNoSpecifiedTests>false</failIfNoSpecifiedTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.alibaba.qwen.code.managedagent.contracts.artifact;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
public interface ArtifactReadLease extends AutoCloseable {
|
||||
public void requireScope(JsonNode key);
|
||||
public void requireScope(String tenant, String session);
|
||||
public void check();
|
||||
public void checkReadView();
|
||||
|
||||
@Override
|
||||
public void close();
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.alibaba.qwen.code.managedagent.contracts.artifact;
|
||||
|
||||
public interface ManagedArtifactPolicy {
|
||||
/** A stable configuration fingerprint that changes whenever a policy decision changes. */
|
||||
public String version();
|
||||
|
||||
public boolean publishOriginal(String tenantId, String workspaceId,
|
||||
String sessionId);
|
||||
|
||||
public boolean publishPreview(String tenantId, String workspaceId,
|
||||
String sessionId);
|
||||
|
||||
public boolean readOriginal(String tenantId, String actorId, String workspaceId,
|
||||
String sessionId);
|
||||
}
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.qwen.code.managedagent.artifact;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.artifact;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
@@ -12,22 +12,22 @@ public interface ToolPublicationObjectStore {
|
||||
* PUT attempt. An existing key must never be overwritten. Once the complete object is readable,
|
||||
* this invocation must not create it again, including after a later deletion.
|
||||
*/
|
||||
void putIfAbsent(String key, byte[] bytes);
|
||||
public void putIfAbsent(String key, byte[] bytes);
|
||||
|
||||
/** The caller closes the stream after verifying its complete contents. */
|
||||
InputStream open(String key);
|
||||
public InputStream open(String key);
|
||||
|
||||
/** Check authorization before each new storage request, including implementation retries. */
|
||||
default InputStream open(String key, Runnable guard) {
|
||||
public default InputStream open(String key, Runnable guard) {
|
||||
guard.run();
|
||||
return open(key);
|
||||
}
|
||||
|
||||
/** Physically delete the exact owned key; an absent object is already deleted. */
|
||||
default void deleteIfPresent(String key) {
|
||||
public default void deleteIfPresent(String key) {
|
||||
throw new UnsupportedOperationException("Output deletion is not supported by this adapter");
|
||||
}
|
||||
|
||||
/** Validate private, durable, immutable storage and exact-key deletion guarantees. */
|
||||
void validateConfiguration();
|
||||
public void validateConfiguration();
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.alibaba.qwen.code.managedagent.contracts.artifact;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
public interface ToolPublicationRetention {
|
||||
public record Candidate(String scope, String publication, String tenant, String session,
|
||||
long bytes, String blocker) { }
|
||||
public List<Candidate> observe(Duration grace);
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.qwen.code.managedagent.artifact;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.artifact;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
+4
-5
@@ -1,11 +1,10 @@
|
||||
package com.alibaba.qwen.code.managedagent.catalog;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.catalog;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.contracts.persistence.ManagedSessionStoreModels;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.workspace.WorkspaceToolProfiles;
|
||||
import com.alibaba.qwen.code.managedagent.exception.ManagedAgentException;
|
||||
import com.alibaba.qwen.code.managedagent.persistence.ManagedSessionStoreModels;
|
||||
import com.alibaba.qwen.code.managedagent.workspace.WorkspaceToolProfiles;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
@@ -173,7 +172,7 @@ public final class AgentDefinitionExtensions {
|
||||
}
|
||||
|
||||
private static ManagedAgentException unsupported(String field) {
|
||||
return new ManagedAgentException(HttpStatus.CONFLICT, "agent_definition_unsupported",
|
||||
return new ManagedAgentException(409, "agent_definition_unsupported",
|
||||
"The agent definition field cannot take effect yet.", Map.of("field", field));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.qwen.code.managedagent.channel;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.channel;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.qwen.code.managedagent.event;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.event;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.model.StoreModels.EventRecord;
|
||||
|
||||
@@ -11,5 +11,5 @@ import java.util.List;
|
||||
* change to the publication contract must be made in both files.
|
||||
*/
|
||||
public interface CommittedEventPublisher {
|
||||
void publish(List<EventRecord> events);
|
||||
public void publish(List<EventRecord> events);
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.alibaba.qwen.code.managedagent.contracts.event;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface EventMessageListener {
|
||||
void consume(byte[] body, String messageId);
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.alibaba.qwen.code.managedagent.contracts.event;
|
||||
|
||||
import java.util.Set;
|
||||
public interface EventSubscriptions extends java.io.Closeable {
|
||||
Set<String> getLiteTopicSet();
|
||||
void subscribeLite(String topic) throws Exception;
|
||||
void unsubscribeLite(String topic) throws Exception;
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.qwen.code.managedagent.event;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.event;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.model.StoreModels.EventRecord;
|
||||
|
||||
@@ -27,5 +27,5 @@ import java.util.List;
|
||||
* change to the publication contract must be made in both files.
|
||||
*/
|
||||
public interface EventTransport {
|
||||
void publish(List<EventRecord> events);
|
||||
public void publish(List<EventRecord> events);
|
||||
}
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
package com.alibaba.qwen.code.managedagent.event;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.event;
|
||||
|
||||
/**
|
||||
* The authoritative-journal wire (design 2026-10-04-managed-agent-event-transport,
|
||||
* phase MQ2b). An after-commit publication point on {@link com.alibaba.qwen.code.managedagent.persistence.ManagedSessionStore#commit}
|
||||
* phase MQ2b). An after-commit publication point on {@link com.alibaba.qwen.code.managedagent.repository.ManagedSessionStore#commit}
|
||||
* hands the committed journal record (the raw record bytes) to this port. It is
|
||||
* distinct from the {@link EventTransport} hub seam, which carries the public
|
||||
* event stream that MQ2c wires as the {@code public_event} envelope extension.
|
||||
@@ -14,5 +14,5 @@ package com.alibaba.qwen.code.managedagent.event;
|
||||
* treat the broker as recovery evidence (the #12380 constraint).
|
||||
*/
|
||||
public interface JournalEventTransport {
|
||||
void publishCommitted(byte[] recordBytes);
|
||||
public void publishCommitted(byte[] recordBytes);
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.qwen.code.managedagent.event;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.event;
|
||||
|
||||
/**
|
||||
* The managed-event-envelope/1 contract mirrored to Java: the distribution
|
||||
+13
-13
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.qwen.code.managedagent.event;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.event;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.model.StoreModels.EventRecord;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
@@ -29,32 +29,32 @@ import java.util.regex.Pattern;
|
||||
* consumers parse both.
|
||||
*/
|
||||
public final class ManagedEventEnvelopes {
|
||||
static final int FORMAT_VERSION = 1;
|
||||
public static final int FORMAT_VERSION = 1;
|
||||
public static final String STREAM_AUTHORITATIVE_JOURNAL =
|
||||
"authoritative_journal";
|
||||
public static final String STREAM_PUBLIC_EVENT = "public_event";
|
||||
// Ordered (the failure message joins it), as the TypeScript constant.
|
||||
static final List<String> STREAMS = List.of(
|
||||
public static final List<String> STREAMS = List.of(
|
||||
STREAM_AUTHORITATIVE_JOURNAL, STREAM_PUBLIC_EVENT);
|
||||
static final int MAX_ID_BYTES = 512;
|
||||
public static final int MAX_ID_BYTES = 512;
|
||||
// A public kind is the public event type: managed_agent_event.event_type
|
||||
// and the public API both cap it at 128, and the grammar is ASCII.
|
||||
static final int MAX_PUBLIC_KIND_BYTES = 128;
|
||||
static final Pattern PUBLIC_KIND = Pattern.compile(
|
||||
public static final int MAX_PUBLIC_KIND_BYTES = 128;
|
||||
public static final Pattern PUBLIC_KIND = Pattern.compile(
|
||||
"^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$");
|
||||
static final long MAX_TIME_MS = 8640000000000000L;
|
||||
public static final long MAX_TIME_MS = 8640000000000000L;
|
||||
// MANAGED_SESSION_LIMITS.maxJsonDepth: the digest refuses a value nested
|
||||
// deeper, counting from the top-level list at depth 0.
|
||||
static final int MAX_JSON_DEPTH = 64;
|
||||
public static final int MAX_JSON_DEPTH = 64;
|
||||
// Number.MAX_SAFE_INTEGER - 1: the schema's sequence ceiling (the TypeScript
|
||||
// assert rejects exactly MAX_SAFE_INTEGER as "cannot advance").
|
||||
static final long MAX_SEQUENCE = 9007199254740990L;
|
||||
public static final long MAX_SEQUENCE = 9007199254740990L;
|
||||
private static final Pattern DIGEST = Pattern.compile("[0-9a-f]{64}");
|
||||
|
||||
public static final String KIND_WAKE_REQUESTED = "wake.requested";
|
||||
// Ordered (the failure message joins it) — the frozen contract's 20-kind
|
||||
// vocabulary in fixture order.
|
||||
static final List<String> KINDS = List.of(
|
||||
public static final List<String> KINDS = List.of(
|
||||
"input.accepted", KIND_WAKE_REQUESTED, "activation.changed",
|
||||
"model.attempt", "message.committed", "tool.intent",
|
||||
"action.changed", "tool.receipt", "checkpoint.committed",
|
||||
@@ -63,7 +63,7 @@ public final class ManagedEventEnvelopes {
|
||||
"message.delta", "message.retracted", "operation.replayed",
|
||||
"hosted.batch.planned", "hosted.cleanup");
|
||||
|
||||
static final Set<String> FORBIDDEN_FIELDS = Set.of(
|
||||
public static final Set<String> FORBIDDEN_FIELDS = Set.of(
|
||||
"absolutePath", "localPath", "pid", "pod", "runtimeBindingId",
|
||||
"runtimeEndpoint", "secretHandle", "sidecar");
|
||||
|
||||
@@ -278,7 +278,7 @@ public final class ManagedEventEnvelopes {
|
||||
|
||||
// --- canonical JSON (mirrors canonicalManagedSessionJson) ---
|
||||
|
||||
static String canonicalJson(JsonNode node) {
|
||||
public static String canonicalJson(JsonNode node) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
appendCanonicalJson(sb, node);
|
||||
return sb.toString();
|
||||
@@ -334,7 +334,7 @@ public final class ManagedEventEnvelopes {
|
||||
* the TypeScript side byte-for-byte. The thresholds are the spec's:
|
||||
* decimal form while 1e-6 ≤ |x| < 1e21, exponential outside it.
|
||||
*/
|
||||
static String jsNumberToString(double d) {
|
||||
public static String jsNumberToString(double d) {
|
||||
if (d == 0) {
|
||||
return "0";
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.alibaba.qwen.code.managedagent.contracts.extensions;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface ExtensionArchives {
|
||||
public boolean configured();
|
||||
public long put(String tenant, String workspace, String digest, InputStream archive) throws IOException;
|
||||
public Path verify(String tenant, String workspace, String digest, long expectedLength) throws IOException;
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.alibaba.qwen.code.managedagent.contracts.extensions;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.contracts.extensions.ManagedExtensionPackages.Package;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.extensions.ManagedExtensionPackages.Pin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface ExtensionPackages {
|
||||
public void requireSelection(String tenant, String actor, String workspace, List<Pin> pins);
|
||||
public ResolvedPackage resolvePin(String tenant, String workspace, Pin pin) throws IOException;
|
||||
public record ResolvedPackage(Package descriptor, Path archive, Map<String, String> settings) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResolvedPackage[descriptor=" + descriptor + ", settings=redacted]";
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.qwen.code.managedagent.extensions;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.extensions;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
+81
-81
@@ -1,6 +1,6 @@
|
||||
package com.alibaba.qwen.code.managedagent.harness;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.harness;
|
||||
|
||||
import com.alibaba.qwen.code.daemon.HarnessRuntimeRecovery;
|
||||
import com.alibaba.qwen.code.managedagent.harness.contracts.HarnessRuntimeRecovery;
|
||||
import com.alibaba.qwen.code.managedagent.model.StoreModels.OperationRecord;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
@@ -8,130 +8,130 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface HarnessConnector extends AutoCloseable {
|
||||
boolean isAvailable();
|
||||
public boolean isAvailable();
|
||||
|
||||
/** Reattaches a SQL-proven unowned Session without trusting the local attachment cache. */
|
||||
default void recoverForWake(String tenantId, String sessionId) {
|
||||
public default void recoverForWake(String tenantId, String sessionId) {
|
||||
throw new UnsupportedOperationException("Hosted wake recovery is unavailable");
|
||||
}
|
||||
|
||||
default boolean supportsPublicChildAgents() { return false; }
|
||||
public default boolean supportsPublicChildAgents() { return false; }
|
||||
|
||||
default String closeSession(OperationRecord operation) {
|
||||
public default String closeSession(OperationRecord operation) {
|
||||
return closeSession(operation.tenantId(), operation.sessionId());
|
||||
}
|
||||
|
||||
default void runLifecycleChildOperation(OperationRecord operation, Map<String, Object> body) {
|
||||
public default void runLifecycleChildOperation(OperationRecord operation, Map<String, Object> body) {
|
||||
runChildOperation(operation.tenantId(), operation.sessionId(), body);
|
||||
}
|
||||
|
||||
default Map<String, Object> drainInstance(String instanceId, String operationId) {
|
||||
public default Map<String, Object> drainInstance(String instanceId, String operationId) {
|
||||
throw new UnsupportedOperationException("Harness drain is unavailable");
|
||||
}
|
||||
|
||||
default Map<String, Object> releaseOwnership(String tenantId, String sessionId,
|
||||
public default Map<String, Object> releaseOwnership(String tenantId, String sessionId,
|
||||
String operationId, long expectedPlacementEpoch) {
|
||||
throw new UnsupportedOperationException("Harness ownership release is unavailable");
|
||||
}
|
||||
|
||||
default boolean supportsApprovalSelection() { return false; }
|
||||
public default boolean supportsApprovalSelection() { return false; }
|
||||
|
||||
default boolean supportsLiveApprovalSelection() { return false; }
|
||||
public default boolean supportsLiveApprovalSelection() { return false; }
|
||||
|
||||
default boolean supportsAutoApproval() { return false; }
|
||||
public default boolean supportsAutoApproval() { return false; }
|
||||
|
||||
default boolean supportsPermissionRules() { return false; }
|
||||
public default boolean supportsPermissionRules() { return false; }
|
||||
|
||||
default boolean supportsAgentDefinitionApprovalSelection() { return false; }
|
||||
public default boolean supportsAgentDefinitionApprovalSelection() { return false; }
|
||||
|
||||
default boolean supportsModelSelection() { return false; }
|
||||
public default boolean supportsModelSelection() { return false; }
|
||||
|
||||
default boolean supportsUserMessageQueue() { return false; }
|
||||
public default boolean supportsUserMessageQueue() { return false; }
|
||||
|
||||
default boolean supportsContextCompression() { return false; }
|
||||
public default boolean supportsContextCompression() { return false; }
|
||||
|
||||
@FunctionalInterface
|
||||
interface ContextCompressionSubmission {
|
||||
Map<String, Object> submit();
|
||||
public interface ContextCompressionSubmission {
|
||||
public Map<String, Object> submit();
|
||||
}
|
||||
|
||||
default ContextCompressionSubmission prepareContextCompression(String tenantId, String sessionId,
|
||||
public default ContextCompressionSubmission prepareContextCompression(String tenantId, String sessionId,
|
||||
Map<String, Object> body) {
|
||||
return () -> runContextCompression(tenantId, sessionId, body);
|
||||
}
|
||||
|
||||
default Map<String, Object> runContextCompression(String tenantId, String sessionId,
|
||||
public default Map<String, Object> runContextCompression(String tenantId, String sessionId,
|
||||
Map<String, Object> body) {
|
||||
throw new UnsupportedOperationException("Context compression is unavailable");
|
||||
}
|
||||
|
||||
default Map<String, Object> readContextCompression(String tenantId, String sessionId,
|
||||
public default Map<String, Object> readContextCompression(String tenantId, String sessionId,
|
||||
String operationId) {
|
||||
throw new UnsupportedOperationException("Context compression is unavailable");
|
||||
}
|
||||
|
||||
default boolean supportsPlanMode() { return false; }
|
||||
public default boolean supportsPlanMode() { return false; }
|
||||
|
||||
default boolean supportsInputAttachments() { return false; }
|
||||
public default boolean supportsInputAttachments() { return false; }
|
||||
|
||||
default boolean supportsSessionSkills() { return false; }
|
||||
public default boolean supportsSessionSkills() { return false; }
|
||||
|
||||
default boolean supportsMcpManagement() { return false; }
|
||||
public default boolean supportsMcpManagement() { return false; }
|
||||
|
||||
default Map<String, Object> mcpManagement(String tenantId, String sessionId) {
|
||||
public default Map<String, Object> mcpManagement(String tenantId, String sessionId) {
|
||||
throw new UnsupportedOperationException("MCP management is unavailable");
|
||||
}
|
||||
|
||||
default Map<String, Object> manageMcp(String tenantId, String sessionId, Map<String, Object> command) {
|
||||
public default Map<String, Object> manageMcp(String tenantId, String sessionId, Map<String, Object> command) {
|
||||
throw new UnsupportedOperationException("MCP management is unavailable");
|
||||
}
|
||||
|
||||
default Map<String, Object> mcpManagementOperation(String tenantId, String sessionId, String operationId) {
|
||||
public default Map<String, Object> mcpManagementOperation(String tenantId, String sessionId, String operationId) {
|
||||
throw new UnsupportedOperationException("MCP management is unavailable");
|
||||
}
|
||||
|
||||
default boolean supportsSessionExtensions() { return false; }
|
||||
public default boolean supportsSessionExtensions() { return false; }
|
||||
|
||||
default Map<String, Object> applyExtensions(String tenantId, String sessionId, Map<String, Object> command) {
|
||||
public default Map<String, Object> applyExtensions(String tenantId, String sessionId, Map<String, Object> command) {
|
||||
throw new UnsupportedOperationException("Extension management is unavailable");
|
||||
}
|
||||
|
||||
default Map<String, Object> continueExtensionOperation(String tenantId, String sessionId, String operationId, boolean recoverPrevious) {
|
||||
public default Map<String, Object> continueExtensionOperation(String tenantId, String sessionId, String operationId, boolean recoverPrevious) {
|
||||
throw new UnsupportedOperationException("Extension management is unavailable");
|
||||
}
|
||||
|
||||
default boolean isWorkspaceFilesAvailable() {
|
||||
public default boolean isWorkspaceFilesAvailable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean supportsLifecycle() { return false; }
|
||||
public default boolean supportsLifecycle() { return false; }
|
||||
|
||||
default boolean supportsLifecycle(String profile) {
|
||||
return com.alibaba.qwen.code.managedagent.workspace.WorkspaceToolProfiles.FILES.equals(profile) && supportsLifecycle();
|
||||
public default boolean supportsLifecycle(String profile) {
|
||||
return com.alibaba.qwen.code.managedagent.contracts.workspace.WorkspaceToolProfiles.FILES.equals(profile) && supportsLifecycle();
|
||||
}
|
||||
|
||||
default void prepareLifecycleResources(com.alibaba.qwen.code.managedagent.model.StoreModels.OperationRecord operation) {
|
||||
public default void prepareLifecycleResources(com.alibaba.qwen.code.managedagent.model.StoreModels.OperationRecord operation) {
|
||||
throw new UnsupportedOperationException("Hosted resource lifecycle is unavailable");
|
||||
}
|
||||
|
||||
default void settleLifecycleResources(com.alibaba.qwen.code.managedagent.model.StoreModels.OperationRecord operation) {
|
||||
public default void settleLifecycleResources(com.alibaba.qwen.code.managedagent.model.StoreModels.OperationRecord operation) {
|
||||
throw new UnsupportedOperationException("Hosted resource lifecycle is unavailable");
|
||||
}
|
||||
|
||||
default boolean supportsFencedTitles() { return false; }
|
||||
public default boolean supportsFencedTitles() { return false; }
|
||||
|
||||
default JsonNode settleLifecycle(com.alibaba.qwen.code.managedagent.model.StoreModels.OperationRecord operation) {
|
||||
public default JsonNode settleLifecycle(com.alibaba.qwen.code.managedagent.model.StoreModels.OperationRecord operation) {
|
||||
throw new UnsupportedOperationException("Hosted lifecycle is unavailable");
|
||||
}
|
||||
|
||||
default void detachLifecycle(com.alibaba.qwen.code.managedagent.model.StoreModels.OperationRecord operation) {
|
||||
public default void detachLifecycle(com.alibaba.qwen.code.managedagent.model.StoreModels.OperationRecord operation) {
|
||||
throw new UnsupportedOperationException("Hosted lifecycle is unavailable");
|
||||
}
|
||||
|
||||
Attachment createOrLoad(String tenantId, String sessionId,
|
||||
public Attachment createOrLoad(String tenantId, String sessionId,
|
||||
boolean loadExisting);
|
||||
|
||||
default Attachment createOrLoad(String tenantId, String sessionId,
|
||||
public default Attachment createOrLoad(String tenantId, String sessionId,
|
||||
boolean loadExisting, boolean passiveManagedRuntimeRecovery) {
|
||||
return createOrLoad(tenantId, sessionId, loadExisting);
|
||||
}
|
||||
@@ -141,13 +141,13 @@ public interface HarnessConnector extends AutoCloseable {
|
||||
* reporting its parked Runtime executions. A plain cold load must stay
|
||||
* inert, so only this path may touch the Broker for a parked Turn.
|
||||
*/
|
||||
default Attachment recoverManagedRuntime(String tenantId, String sessionId,
|
||||
public default Attachment recoverManagedRuntime(String tenantId, String sessionId,
|
||||
boolean cancellation) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Managed Runtime recovery is unavailable");
|
||||
}
|
||||
|
||||
default Attachment recoverManagedRuntime(String tenantId, String sessionId,
|
||||
public default Attachment recoverManagedRuntime(String tenantId, String sessionId,
|
||||
boolean cancellation, String promptId) {
|
||||
throw new UnsupportedOperationException("Targeted Runtime recovery is unavailable");
|
||||
}
|
||||
@@ -159,24 +159,24 @@ public interface HarnessConnector extends AutoCloseable {
|
||||
* while the healthy-attachment shortcut would answer from cache and
|
||||
* never tell the daemon (R9-P1-2).
|
||||
*/
|
||||
default Attachment recoverManagedCancellation(String tenantId,
|
||||
public default Attachment recoverManagedCancellation(String tenantId,
|
||||
String sessionId) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Managed Runtime recovery is unavailable");
|
||||
}
|
||||
|
||||
default Attachment recoverManagedCancellation(String tenantId,
|
||||
public default Attachment recoverManagedCancellation(String tenantId,
|
||||
String sessionId, String promptId) {
|
||||
throw new UnsupportedOperationException("Targeted Runtime recovery is unavailable");
|
||||
}
|
||||
|
||||
default Attachment recoverManagedFailureCancellation(String tenantId,
|
||||
public default Attachment recoverManagedFailureCancellation(String tenantId,
|
||||
String sessionId, String turnId, String dispatchOwner) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Managed Runtime recovery is unavailable");
|
||||
}
|
||||
|
||||
default Attachment recoverManagedFailureCancellation(String tenantId,
|
||||
public default Attachment recoverManagedFailureCancellation(String tenantId,
|
||||
String sessionId, String turnId, String dispatchOwner, String promptId) {
|
||||
throw new UnsupportedOperationException("Targeted Runtime recovery is unavailable");
|
||||
}
|
||||
@@ -186,53 +186,53 @@ public interface HarnessConnector extends AutoCloseable {
|
||||
* recovery load re-reads the parked Runtime state instead of answering
|
||||
* from a report already known to be stale.
|
||||
*/
|
||||
default void discardManagedRecovery(String tenantId, String sessionId) {
|
||||
public default void discardManagedRecovery(String tenantId, String sessionId) {
|
||||
}
|
||||
|
||||
record RecoveryStatus(String state, String reasonCode, String promptId,
|
||||
public record RecoveryStatus(String state, String reasonCode, String promptId,
|
||||
boolean inputSettled, boolean continuationReady, boolean nextTurnAllowed) {}
|
||||
|
||||
/** Inspects an existing attachment without loading or acquiring anything. */
|
||||
default RecoveryStatus inspectRecovery(String tenantId, String sessionId, String promptId) {
|
||||
public default RecoveryStatus inspectRecovery(String tenantId, String sessionId, String promptId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Returns only cached replay identity, without loading or acquiring anything. */
|
||||
default Attachment cachedRecoveryAttachment(String tenantId, String sessionId) {
|
||||
public default Attachment cachedRecoveryAttachment(String tenantId, String sessionId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Returns false when the original terminal cleanup cannot safely be retried. */
|
||||
default boolean recheckRecoveryCleanup(String tenantId, String sessionId, String promptId) {
|
||||
public default boolean recheckRecoveryCleanup(String tenantId, String sessionId, String promptId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Admission submit(String tenantId, String sessionId, String promptId,
|
||||
public Admission submit(String tenantId, String sessionId, String promptId,
|
||||
List<Map<String, Object>> input, String payloadDigest);
|
||||
|
||||
default Admission continueManagedRuntime(String tenantId,
|
||||
public default Admission continueManagedRuntime(String tenantId,
|
||||
String sessionId, String promptId, String checkpointId,
|
||||
String activationId) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Managed Runtime continuation is unavailable");
|
||||
}
|
||||
|
||||
default Admission cancelManagedRuntime(String tenantId, String sessionId,
|
||||
public default Admission cancelManagedRuntime(String tenantId, String sessionId,
|
||||
String promptId, String checkpointId, String activationId) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Managed Runtime cancellation is unavailable");
|
||||
}
|
||||
|
||||
SourceStream stream(String tenantId, String sessionId, long lastEventId,
|
||||
public SourceStream stream(String tenantId, String sessionId, long lastEventId,
|
||||
String eventEpoch);
|
||||
|
||||
/** Reads only an existing original attachment; it must never load or take over execution. */
|
||||
default SourceStream observeStream(String tenantId, String sessionId, String bootId,
|
||||
public default SourceStream observeStream(String tenantId, String sessionId, String bootId,
|
||||
long lastEventId, String eventEpoch) {
|
||||
throw new UnsupportedOperationException("Read-only Harness observation is unavailable");
|
||||
}
|
||||
|
||||
default void resolveAction(
|
||||
public default void resolveAction(
|
||||
String tenantId,
|
||||
String sessionId,
|
||||
String actionId,
|
||||
@@ -240,9 +240,9 @@ public interface HarnessConnector extends AutoCloseable {
|
||||
throw new UnsupportedOperationException("Hosted Actions are unavailable");
|
||||
}
|
||||
|
||||
void cancel(String tenantId, String sessionId);
|
||||
public void cancel(String tenantId, String sessionId);
|
||||
|
||||
default void cancelOriginalTurn(String tenantId, String sessionId, String promptId) {
|
||||
public default void cancelOriginalTurn(String tenantId, String sessionId, String promptId) {
|
||||
throw new UnsupportedOperationException("Targeted cancellation is unavailable");
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ public interface HarnessConnector extends AutoCloseable {
|
||||
* carries the committed operation result; a cancel_run may still be
|
||||
* awaiting its Turn's physical settlement.
|
||||
*/
|
||||
default Map<String, Object> runAutomationOperation(String tenantId,
|
||||
public default Map<String, Object> runAutomationOperation(String tenantId,
|
||||
String sessionId, Map<String, Object> body) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Automation operations are unavailable");
|
||||
@@ -264,7 +264,7 @@ public interface HarnessConnector extends AutoCloseable {
|
||||
* control plane's relay (dispatch/attach/result/accept/cancel/
|
||||
* close-scope). The Hosted side settles it before answering.
|
||||
*/
|
||||
default void runChildOperation(String tenantId, String sessionId,
|
||||
public default void runChildOperation(String tenantId, String sessionId,
|
||||
Map<String, Object> body) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Child operations are unavailable");
|
||||
@@ -281,7 +281,7 @@ public interface HarnessConnector extends AutoCloseable {
|
||||
* names a command id, and a settled {@code execute} answers with the
|
||||
* reply the relay copies back.
|
||||
*/
|
||||
default Map<String, Object> runMessageOperation(String tenantId,
|
||||
public default Map<String, Object> runMessageOperation(String tenantId,
|
||||
String sessionId, Map<String, Object> body) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Message operations are unavailable");
|
||||
@@ -294,15 +294,15 @@ public interface HarnessConnector extends AutoCloseable {
|
||||
* The Hosted side settles it before answering, and the answer carries
|
||||
* the operation's result.
|
||||
*/
|
||||
default Map<String, Object> runChannelOperation(String tenantId,
|
||||
public default Map<String, Object> runChannelOperation(String tenantId,
|
||||
String sessionId, Map<String, Object> body) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Channel operations are unavailable");
|
||||
}
|
||||
|
||||
void rename(String tenantId, String sessionId, String title);
|
||||
public void rename(String tenantId, String sessionId, String title);
|
||||
|
||||
default void rename(String tenantId, String sessionId, String title, long revision) {
|
||||
public default void rename(String tenantId, String sessionId, String title, long revision) {
|
||||
throw new UnsupportedOperationException("Fenced Session titles are unavailable");
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ public interface HarnessConnector extends AutoCloseable {
|
||||
* Closes the Session and returns the boot ID of the Harness that
|
||||
* answered. A Harness that does not hold the Session answers too.
|
||||
*/
|
||||
String closeSession(String tenantId, String sessionId);
|
||||
public String closeSession(String tenantId, String sessionId);
|
||||
|
||||
/**
|
||||
* The Sessions this control-plane node holds an attachment for. An
|
||||
@@ -320,7 +320,7 @@ public interface HarnessConnector extends AutoCloseable {
|
||||
* narrows it by SQL to the Sessions whose journal this node's Harness
|
||||
* writes. A connector without attachments holds nothing.
|
||||
*/
|
||||
default List<AttachedSession> attachedSessions() {
|
||||
public default List<AttachedSession> attachedSessions() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@@ -328,7 +328,7 @@ public interface HarnessConnector extends AutoCloseable {
|
||||
* The writer id this node's Harness acquires Session journals under, or
|
||||
* null while there is none (no client yet, or the connector is closed).
|
||||
*/
|
||||
default String writerIdentity() {
|
||||
public default String writerIdentity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -339,7 +339,7 @@ public interface HarnessConnector extends AutoCloseable {
|
||||
* take. Returns false, loading nothing, when this node already holds an
|
||||
* attachment for the Session.
|
||||
*/
|
||||
default boolean activateForWake(String tenantId, String sessionId) {
|
||||
public default boolean activateForWake(String tenantId, String sessionId) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Hosted wake activation is unavailable");
|
||||
}
|
||||
@@ -348,13 +348,13 @@ public interface HarnessConnector extends AutoCloseable {
|
||||
default void close() {
|
||||
}
|
||||
|
||||
record AttachedSession(String tenantId, String sessionId, String writerIdentity, long placementEpoch) {
|
||||
public record AttachedSession(String tenantId, String sessionId, String writerIdentity, long placementEpoch) {
|
||||
public AttachedSession(String tenantId, String sessionId) {
|
||||
this(tenantId, sessionId, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
record Attachment(String bootId, HarnessRuntimeRecovery runtimeRecovery,
|
||||
public record Attachment(String bootId, HarnessRuntimeRecovery runtimeRecovery,
|
||||
Long lastEventId, String eventEpoch) {
|
||||
public Attachment(String bootId) {
|
||||
this(bootId, null, null, null);
|
||||
@@ -366,23 +366,23 @@ public interface HarnessConnector extends AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
record Admission(long lastEventId, String eventEpoch) {
|
||||
public record Admission(long lastEventId, String eventEpoch) {
|
||||
}
|
||||
|
||||
record SourceEvent(Long id, String type, Object data, String promptId,
|
||||
public record SourceEvent(Long id, String type, Object data, String promptId,
|
||||
Map<String, Object> metadata) {
|
||||
}
|
||||
|
||||
interface SourceStream extends AutoCloseable {
|
||||
String eventEpoch();
|
||||
public interface SourceStream extends AutoCloseable {
|
||||
public String eventEpoch();
|
||||
|
||||
SourceEvent next();
|
||||
public SourceEvent next();
|
||||
|
||||
default boolean recoveryBlocked(long afterEventId) {
|
||||
public default boolean recoveryBlocked(long afterEventId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void close();
|
||||
public void close();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.qwen.code.managedagent.harness;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.harness;
|
||||
|
||||
// Signals a Harness disabled by configuration rather than a transient
|
||||
// failure, so the coordinator can hold the Turn instead of burning its
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.qwen.code.managedagent.harness;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.harness;
|
||||
|
||||
/**
|
||||
* The Hosted Harness refused a takeover load with a typed, terminal reason:
|
||||
+8
-9
@@ -1,11 +1,10 @@
|
||||
package com.alibaba.qwen.code.managedagent.persistence;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.persistence;
|
||||
|
||||
import com.alibaba.qwen.code.managedagent.catalog.AgentDefinitionExtensions;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.catalog.AgentDefinitionExtensions;
|
||||
import com.alibaba.qwen.code.managedagent.contracts.workspace.WorkspaceToolProfiles;
|
||||
import com.alibaba.qwen.code.managedagent.exception.ManagedAgentException;
|
||||
import com.alibaba.qwen.code.managedagent.workspace.WorkspaceToolProfiles;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
@@ -18,7 +17,7 @@ import java.util.Map;
|
||||
* admission with {@code 409 agent_definition_unsupported} naming the field,
|
||||
* instead of being dropped.
|
||||
*/
|
||||
final class AgentDefinitionCompiler {
|
||||
public final class AgentDefinitionCompiler {
|
||||
private static final ObjectMapper JSON = new ObjectMapper();
|
||||
private static final TypeReference<Map<String, Object>> CONTENT =
|
||||
new TypeReference<>() {
|
||||
@@ -26,19 +25,19 @@ final class AgentDefinitionCompiler {
|
||||
private static final List<String> APPROVAL_MODES = List.of("default",
|
||||
"auto-edit", "yolo");
|
||||
|
||||
record Compiled(String approvalMode, String toolProfile) {
|
||||
public record Compiled(String approvalMode, String toolProfile) {
|
||||
}
|
||||
|
||||
private AgentDefinitionCompiler() {
|
||||
}
|
||||
|
||||
static Compiled compile(String definitionJson, boolean workspaceBound,
|
||||
public static Compiled compile(String definitionJson, boolean workspaceBound,
|
||||
String deploymentApprovalMode, List<String> toolProfiles) {
|
||||
return compile(definitionJson, workspaceBound,
|
||||
deploymentApprovalMode, toolProfiles, List.of());
|
||||
}
|
||||
|
||||
static Compiled compile(String definitionJson, boolean workspaceBound,
|
||||
public static Compiled compile(String definitionJson, boolean workspaceBound,
|
||||
String deploymentApprovalMode, List<String> toolProfiles,
|
||||
List<String> models) {
|
||||
Map<String, Object> definition;
|
||||
@@ -121,7 +120,7 @@ final class AgentDefinitionCompiler {
|
||||
}
|
||||
|
||||
private static ManagedAgentException unsupported(String field) {
|
||||
return new ManagedAgentException(HttpStatus.CONFLICT,
|
||||
return new ManagedAgentException(409,
|
||||
"agent_definition_unsupported",
|
||||
"The agent definition field cannot take effect yet.",
|
||||
Map.of("field", field));
|
||||
+10
-10
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.qwen.code.managedagent.persistence;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.persistence;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -19,7 +19,7 @@ public interface ChannelDeliveryRepository {
|
||||
"delivered", "unknown", "rejected", "cancelled");
|
||||
|
||||
/** One outbound delivery. */
|
||||
record ChannelDelivery(
|
||||
public record ChannelDelivery(
|
||||
String tenantId,
|
||||
String channelInstanceId,
|
||||
String deliveryId,
|
||||
@@ -32,18 +32,18 @@ public interface ChannelDeliveryRepository {
|
||||
}
|
||||
|
||||
/** Exclusive page cursor: created time and delivery id of the last row. */
|
||||
record DeliveryCursor(long createdAt, String deliveryId) {
|
||||
public record DeliveryCursor(long createdAt, String deliveryId) {
|
||||
}
|
||||
|
||||
/** One newest-first page of deliveries. */
|
||||
record DeliveryPage(List<ChannelDelivery> deliveries, boolean hasMore) {
|
||||
public record DeliveryPage(List<ChannelDelivery> deliveries, boolean hasMore) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the shared delivery line allows one step from {@code from} to
|
||||
* {@code to} for a channel target.
|
||||
*/
|
||||
static boolean isLegalStep(String from, String to) {
|
||||
public static boolean isLegalStep(String from, String to) {
|
||||
return DELIVERY_STATES.contains(to)
|
||||
&& ManagedExtensionRecords.TRANSITIONS.get("delivery")
|
||||
.getOrDefault(from, List.of()).contains(to);
|
||||
@@ -54,9 +54,9 @@ public interface ChannelDeliveryRepository {
|
||||
* the row its deliveryId already committed. A duplicate id bound to
|
||||
* another segment fails instead of silently joining it.
|
||||
*/
|
||||
ChannelDelivery findOrCreate(ChannelDelivery candidate);
|
||||
public ChannelDelivery findOrCreate(ChannelDelivery candidate);
|
||||
|
||||
Optional<ChannelDelivery> find(String tenantId, String channelInstanceId,
|
||||
public Optional<ChannelDelivery> find(String tenantId, String channelInstanceId,
|
||||
String deliveryId);
|
||||
|
||||
/**
|
||||
@@ -67,11 +67,11 @@ public interface ChannelDeliveryRepository {
|
||||
* current row as the idempotent replay of this step; null when the row
|
||||
* is absent or holds any other state.
|
||||
*/
|
||||
ChannelDelivery transition(String tenantId, String channelInstanceId,
|
||||
public ChannelDelivery transition(String tenantId, String channelInstanceId,
|
||||
String deliveryId, String expectedState, String state,
|
||||
String providerReceipt);
|
||||
|
||||
ChannelDelivery transition(String tenantId, String channelInstanceId,
|
||||
public ChannelDelivery transition(String tenantId, String channelInstanceId,
|
||||
String deliveryId, String expectedState, String state,
|
||||
String providerReceipt, long claimGeneration);
|
||||
|
||||
@@ -79,6 +79,6 @@ public interface ChannelDeliveryRepository {
|
||||
* Newest first by (createdAt, deliveryId) descending; {@code before}
|
||||
* excludes its own row. {@code limit} must be in [1, 100].
|
||||
*/
|
||||
DeliveryPage listByChannel(String tenantId, String channelInstanceId,
|
||||
public DeliveryPage listByChannel(String tenantId, String channelInstanceId,
|
||||
DeliveryCursor before, int limit);
|
||||
}
|
||||
+9
-9
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.qwen.code.managedagent.persistence;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.persistence;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -17,7 +17,7 @@ public interface ChannelRouteRepository {
|
||||
String SEPARATOR = Character.toString((char) 0);
|
||||
|
||||
/** One committed route binding. */
|
||||
record ChannelRoute(
|
||||
public record ChannelRoute(
|
||||
String tenantId,
|
||||
String routeKey,
|
||||
String channelInstanceId,
|
||||
@@ -36,15 +36,15 @@ public interface ChannelRouteRepository {
|
||||
}
|
||||
|
||||
/** Exclusive page cursor: created time and route key of the last row. */
|
||||
record RouteCursor(long createdAt, String routeKey) {
|
||||
public record RouteCursor(long createdAt, String routeKey) {
|
||||
}
|
||||
|
||||
/** One newest-first page of route bindings. */
|
||||
record RoutePage(List<ChannelRoute> routes, boolean hasMore) {
|
||||
public record RoutePage(List<ChannelRoute> routes, boolean hasMore) {
|
||||
}
|
||||
|
||||
/** The primary key beside the tenant: the binding identity digest. */
|
||||
static String routeKey(String tenantId, String channelInstanceId,
|
||||
public static String routeKey(String tenantId, String channelInstanceId,
|
||||
long accountGeneration, String platformEventId,
|
||||
long semanticRevision) {
|
||||
if (tenantId.contains(SEPARATOR) || channelInstanceId.contains(
|
||||
@@ -62,21 +62,21 @@ public interface ChannelRouteRepository {
|
||||
* committed. A candidate whose identity matches an existing row adds
|
||||
* nothing, so ingress redelivery of one platform event is one binding.
|
||||
*/
|
||||
ChannelRoute findOrCreate(ChannelRoute candidate);
|
||||
public ChannelRoute findOrCreate(ChannelRoute candidate);
|
||||
|
||||
Optional<ChannelRoute> find(String tenantId, String routeKey);
|
||||
public Optional<ChannelRoute> find(String tenantId, String routeKey);
|
||||
|
||||
/**
|
||||
* Records the input a staged route's Session admitted. Returns null when
|
||||
* the binding is absent or already admitted with another input; a replay
|
||||
* with the same input returns the current row.
|
||||
*/
|
||||
ChannelRoute admit(String tenantId, String routeKey, String inputId);
|
||||
public ChannelRoute admit(String tenantId, String routeKey, String inputId);
|
||||
|
||||
/**
|
||||
* Newest first by (createdAt, routeKey) descending; {@code before}
|
||||
* excludes its own row. {@code limit} must be in [1, 100].
|
||||
*/
|
||||
RoutePage listByChannel(String tenantId, String channelInstanceId,
|
||||
public RoutePage listByChannel(String tenantId, String channelInstanceId,
|
||||
RouteCursor before, int limit);
|
||||
}
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.qwen.code.managedagent.persistence;
|
||||
package com.alibaba.qwen.code.managedagent.contracts.persistence;
|
||||
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -6,15 +6,15 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/** Shared identity helpers of the H5 channel persistence. */
|
||||
final class ChannelStoreSupport {
|
||||
public final class ChannelStoreSupport {
|
||||
private ChannelStoreSupport() {
|
||||
}
|
||||
|
||||
static String sha256(String value) {
|
||||
public static String sha256(String value) {
|
||||
return DigestUtils.sha256Hex(value.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
static String requireId(String value, String name) {
|
||||
public static String requireId(String value, String name) {
|
||||
if (StringUtils.isBlank(value)) {
|
||||
throw new IllegalArgumentException(name + " is required");
|
||||
}
|
||||
Loaded 100 of 1231 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user