mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-10-28 02:06:00 +00:00
Merge branch 'main' into feat/bun-pty-backend
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
commit
ddc7666b9f
2054 changed files with 244636 additions and 33380 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
|
@ -9,6 +9,7 @@
|
|||
*.bash eol=lf
|
||||
Makefile eol=lf
|
||||
packages/vscode-ide-companion/NOTICES.txt linguist-generated=true
|
||||
packages/web-shell/NOTICES.txt linguist-generated=true whitespace=-blank-at-eof
|
||||
|
||||
# Windows cmd.exe expects batch installers to be checked out with CRLF.
|
||||
scripts/installation/install-qwen-standalone.bat text eol=crlf
|
||||
|
|
|
|||
7
.github/CODEOWNERS
vendored
7
.github/CODEOWNERS
vendored
|
|
@ -3,7 +3,12 @@
|
|||
# ============================================================
|
||||
|
||||
# --- CODEOWNERS file itself ---
|
||||
/.github/CODEOWNERS @pomelo-nwu @wenshao
|
||||
/.github/CODEOWNERS @pomelo-nwu @wenshao @yiliang114
|
||||
|
||||
# --- GitHub automation ---
|
||||
/.github/workflows/ @pomelo-nwu @wenshao @yiliang114
|
||||
/.github/actions/ @pomelo-nwu @wenshao @yiliang114
|
||||
/.github/scripts/ @pomelo-nwu @wenshao @yiliang114
|
||||
|
||||
# --- Primary npm release workflows require core maintainer approval ---
|
||||
/.github/workflows/release.yml @pomelo-nwu @wenshao
|
||||
|
|
|
|||
1
.github/actionlint.yaml
vendored
1
.github/actionlint.yaml
vendored
|
|
@ -2,6 +2,7 @@ self-hosted-runner:
|
|||
labels:
|
||||
- 'ecs-qwen'
|
||||
- 'ecs-qwen-hk4-host'
|
||||
- 'ecs-qwen-release-host'
|
||||
- 'ecs-win'
|
||||
- 'ecs-update-hk-1'
|
||||
- 'ecs-update-hk-2'
|
||||
|
|
|
|||
89
.github/assets/qwen-code-avg-score-trend.svg
vendored
89
.github/assets/qwen-code-avg-score-trend.svg
vendored
|
|
@ -1,37 +1,56 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1120" height="430" viewBox="0 0 1120 430">
|
||||
<rect width="100%" height="100%" fill="#FFFFFF"/>
|
||||
<g font-family="Arial, sans-serif">
|
||||
<line x1="70" y1="368" x2="1082" y2="368" stroke="#D8E0EA" stroke-width="1"/>
|
||||
<text x="56" y="373" text-anchor="end" font-size="14" fill="#52606D">76.0%</text>
|
||||
<line x1="70" y1="285.5" x2="1082" y2="285.5" stroke="#D8E0EA" stroke-width="1"/>
|
||||
<text x="56" y="290.5" text-anchor="end" font-size="14" fill="#52606D">76.5%</text>
|
||||
<line x1="70" y1="203" x2="1082" y2="203" stroke="#D8E0EA" stroke-width="1"/>
|
||||
<text x="56" y="208" text-anchor="end" font-size="14" fill="#52606D">77.0%</text>
|
||||
<line x1="70" y1="120.5" x2="1082" y2="120.5" stroke="#D8E0EA" stroke-width="1"/>
|
||||
<text x="56" y="125.5" text-anchor="end" font-size="14" fill="#52606D">77.5%</text>
|
||||
<line x1="70" y1="38" x2="1082" y2="38" stroke="#D8E0EA" stroke-width="1"/>
|
||||
<text x="56" y="43" text-anchor="end" font-size="14" fill="#52606D">78.0%</text>
|
||||
<path d="M 70 71 L 238.7 247.6 L 407.3 269 L 576 290.5 L 744.7 92.5 L 913.3 302 L 1082 148.6" fill="none" stroke="#1F6FEB" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="70" cy="71" r="6" fill="#1F6FEB" stroke="#FFFFFF" stroke-width="3"/>
|
||||
<text x="70" y="55" text-anchor="middle" font-size="14" font-weight="700" fill="#16324F">77.80%</text>
|
||||
<text x="70" y="407" text-anchor="middle" font-size="14" fill="#344054">0.0.14</text>
|
||||
<circle cx="238.7" cy="247.6" r="6" fill="#1F6FEB" stroke="#FFFFFF" stroke-width="3"/>
|
||||
<text x="238.7" y="231.6" text-anchor="middle" font-size="14" font-weight="700" fill="#16324F">76.73%</text>
|
||||
<text x="238.7" y="407" text-anchor="middle" font-size="14" fill="#344054">0.1.0</text>
|
||||
<circle cx="407.3" cy="269" r="6" fill="#1F6FEB" stroke="#FFFFFF" stroke-width="3"/>
|
||||
<text x="407.3" y="253" text-anchor="middle" font-size="14" font-weight="700" fill="#16324F">76.60%</text>
|
||||
<text x="407.3" y="407" text-anchor="middle" font-size="14" fill="#344054">0.5.0</text>
|
||||
<circle cx="576" cy="290.5" r="6" fill="#1F6FEB" stroke="#FFFFFF" stroke-width="3"/>
|
||||
<text x="576" y="274.5" text-anchor="middle" font-size="14" font-weight="700" fill="#16324F">76.47%</text>
|
||||
<text x="576" y="407" text-anchor="middle" font-size="14" fill="#344054">0.10.0</text>
|
||||
<circle cx="744.7" cy="92.5" r="6" fill="#1F6FEB" stroke="#FFFFFF" stroke-width="3"/>
|
||||
<text x="744.7" y="76.5" text-anchor="middle" font-size="14" font-weight="700" fill="#16324F">77.67%</text>
|
||||
<text x="744.7" y="407" text-anchor="middle" font-size="14" fill="#344054">0.15.0</text>
|
||||
<circle cx="913.3" cy="302" r="6" fill="#1F6FEB" stroke="#FFFFFF" stroke-width="3"/>
|
||||
<text x="913.3" y="286" text-anchor="middle" font-size="14" font-weight="700" fill="#16324F">76.40%</text>
|
||||
<text x="913.3" y="407" text-anchor="middle" font-size="14" fill="#344054">0.19.0</text>
|
||||
<circle cx="1082" cy="148.6" r="6" fill="#1F6FEB" stroke="#FFFFFF" stroke-width="3"/>
|
||||
<text x="1082" y="132.6" text-anchor="middle" font-size="14" font-weight="700" fill="#16324F">77.33%</text>
|
||||
<text x="1082" y="407" text-anchor="middle" font-size="14" fill="#344054">0.22.0</text>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="840" height="220" viewBox="0 0 840 220" role="img" aria-labelledby="chart-title chart-description">
|
||||
<title id="chart-title">Average SWE-bench Verified score by Qwen Code version</title>
|
||||
<desc id="chart-description">Qwen 3.7 Max, 500 tasks, three trials per version. Average scores: 0.0.14, 77.80%; 0.1.0, 76.73%; 0.5.0, 76.60%; 0.10.0, 76.47%; 0.15.0, 77.67%; 0.19.0, 76.40%; 0.22.0, 77.33%. The line connects evaluated versions in release order. The vertical axis spans 70% to 80%.</desc>
|
||||
<style>
|
||||
.background { fill: #ffffff; }
|
||||
.heading, .score { fill: #1f2328; }
|
||||
.label { fill: #59636e; }
|
||||
.grid { stroke: #e5e7eb; }
|
||||
.axis { stroke: #9ca3af; }
|
||||
.trend { stroke: #6b4eff; }
|
||||
.point { fill: #6b4eff; stroke: #ffffff; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.background { fill: #0d1117; }
|
||||
.heading, .score { fill: #f0f6fc; }
|
||||
.label { fill: #b1bac4; }
|
||||
.grid { stroke: #30363d; }
|
||||
.axis { stroke: #6e7681; }
|
||||
.trend { stroke: #a78bfa; }
|
||||
.point { fill: #a78bfa; stroke: #0d1117; }
|
||||
}
|
||||
</style>
|
||||
<rect class="background" width="840" height="220"/>
|
||||
<g font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif">
|
||||
<text class="heading" x="24" y="28" font-size="20" font-weight="600">Average score by version</text>
|
||||
<text class="label" x="24" y="51" font-size="14">SWE-bench Verified · Qwen 3.7 Max · y-axis: 70–80%</text>
|
||||
<line class="grid" x1="84" y1="96" x2="816" y2="96" stroke-width="1"/>
|
||||
<text class="label" x="70" y="101" text-anchor="end" font-size="14">80%</text>
|
||||
<line class="grid" x1="84" y1="142" x2="816" y2="142" stroke-width="1"/>
|
||||
<text class="label" x="70" y="147" text-anchor="end" font-size="14">75%</text>
|
||||
<line class="axis" x1="84" y1="188" x2="816" y2="188" stroke-width="1"/>
|
||||
<text class="label" x="70" y="193" text-anchor="end" font-size="14">70%</text>
|
||||
<line class="axis" x1="84" y1="96" x2="84" y2="188" stroke-width="1"/>
|
||||
<path class="trend" d="M 140 116.2400 L 244 126.0840 L 348 127.2800 L 452 128.4760 L 556 117.4360 L 660 129.1200 L 764 120.5640" fill="none" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle class="point" cx="140" cy="116.2400" r="4.5" stroke-width="2"/>
|
||||
<text class="score" x="140" y="82" text-anchor="middle" font-size="18" font-weight="600">77.80%</text>
|
||||
<text class="label" x="140" y="214" text-anchor="middle" font-size="16">0.0.14</text>
|
||||
<circle class="point" cx="244" cy="126.0840" r="4.5" stroke-width="2"/>
|
||||
<text class="score" x="244" y="82" text-anchor="middle" font-size="18" font-weight="600">76.73%</text>
|
||||
<text class="label" x="244" y="214" text-anchor="middle" font-size="16">0.1.0</text>
|
||||
<circle class="point" cx="348" cy="127.2800" r="4.5" stroke-width="2"/>
|
||||
<text class="score" x="348" y="82" text-anchor="middle" font-size="18" font-weight="600">76.60%</text>
|
||||
<text class="label" x="348" y="214" text-anchor="middle" font-size="16">0.5.0</text>
|
||||
<circle class="point" cx="452" cy="128.4760" r="4.5" stroke-width="2"/>
|
||||
<text class="score" x="452" y="82" text-anchor="middle" font-size="18" font-weight="600">76.47%</text>
|
||||
<text class="label" x="452" y="214" text-anchor="middle" font-size="16">0.10.0</text>
|
||||
<circle class="point" cx="556" cy="117.4360" r="4.5" stroke-width="2"/>
|
||||
<text class="score" x="556" y="82" text-anchor="middle" font-size="18" font-weight="600">77.67%</text>
|
||||
<text class="label" x="556" y="214" text-anchor="middle" font-size="16">0.15.0</text>
|
||||
<circle class="point" cx="660" cy="129.1200" r="4.5" stroke-width="2"/>
|
||||
<text class="score" x="660" y="82" text-anchor="middle" font-size="18" font-weight="600">76.40%</text>
|
||||
<text class="label" x="660" y="214" text-anchor="middle" font-size="16">0.19.0</text>
|
||||
<circle class="point" cx="764" cy="120.5640" r="4.5" stroke-width="2"/>
|
||||
<text class="score" x="764" y="82" text-anchor="middle" font-size="18" font-weight="600">77.33%</text>
|
||||
<text class="label" x="764" y="214" text-anchor="middle" font-size="16">0.22.0</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 4.1 KiB |
3
.github/dependabot.yml
vendored
3
.github/dependabot.yml
vendored
|
|
@ -37,8 +37,7 @@ updates:
|
|||
- package-ecosystem: 'maven'
|
||||
directories:
|
||||
- '/packages/sdk-java/qwencode'
|
||||
- '/packages/sdk-java/runtime-broker'
|
||||
- '/packages/sdk-java/managed-agent-server'
|
||||
- '/packages/managed-agent-server'
|
||||
- '/packages/sdk-java/client'
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
|
|
|
|||
107
.github/scripts/check-ci-budget.mjs
vendored
Normal file
107
.github/scripts/check-ci-budget.mjs
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#!/usr/bin/env node
|
||||
import { readFileSync, readdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
// Dependency-free: workflows use block mappings with two-space indentation.
|
||||
// Reject unsupported headers instead of silently excluding them from the gate.
|
||||
function block(source, key) {
|
||||
const lines = source.split('\n');
|
||||
const start = lines.findIndex((line) => key.test(line));
|
||||
if (start < 0 || !/:\s*(?:#.*)?$/.test(lines[start])) {
|
||||
throw new Error('Expected block-style on/jobs mapping');
|
||||
}
|
||||
const end = lines.findIndex(
|
||||
(line, index) => index > start && /^\S/.test(line) && !line.startsWith('#'),
|
||||
);
|
||||
return lines.slice(start + 1, end < 0 ? undefined : end);
|
||||
}
|
||||
|
||||
export function measureWorkflow(source) {
|
||||
const triggers = block(source, /^(?:on|true|'on'|"on"):/);
|
||||
if (
|
||||
!triggers.some((line) =>
|
||||
/^ {2}(['"]?)(pull_request|pull_request_target|merge_group)\1\s*:/.test(
|
||||
line,
|
||||
),
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const jobs = [];
|
||||
for (const line of block(source, /^jobs:/)) {
|
||||
if (/^ {2}[\w-]+:\s*(?:#.*)?$/.test(line)) jobs.push(null);
|
||||
else if (/^ {2}[^#\s]/.test(line))
|
||||
throw new Error('Expected block-style job');
|
||||
const timeoutKey = /^ {4}(['"]?)timeout-minutes\1\s*:\s*/;
|
||||
if (timeoutKey.test(line)) {
|
||||
const timeout = /^(\d+)\s*(?:#.*)?$/.exec(line.replace(timeoutKey, ''));
|
||||
// Expressions have no fixed declared budget; count them as unbounded.
|
||||
if (timeout && jobs.length) jobs[jobs.length - 1] = Number(timeout[1]);
|
||||
}
|
||||
}
|
||||
if (!jobs.length) throw new Error('No jobs found');
|
||||
return [
|
||||
jobs.length,
|
||||
jobs.reduce((sum, timeout) => sum + (timeout ?? 0), 0),
|
||||
jobs.filter((timeout) => timeout === null).length,
|
||||
];
|
||||
}
|
||||
|
||||
export function checkBudget(workflowDir = '.github/workflows') {
|
||||
const baseline = new Map();
|
||||
for (const line of readFileSync(
|
||||
join(workflowDir, '.ci-budget'),
|
||||
'utf8',
|
||||
).split('\n')) {
|
||||
if (!line.trim() || line.trim().startsWith('#')) continue;
|
||||
const match = /^(\d+)\s+(\d+)\s+(\d+)\s+(\S+\.ya?ml)$/.exec(line.trim());
|
||||
if (!match || baseline.has(match[4]))
|
||||
throw new Error(`Invalid budget entry: ${line}`);
|
||||
baseline.set(match[4], match.slice(1, 4).map(Number));
|
||||
}
|
||||
const errors = [];
|
||||
const labels = ['jobs', 'timeout-minutes', 'jobs without fixed timeout'];
|
||||
for (const file of readdirSync(workflowDir).sort()) {
|
||||
if (!/\.ya?ml$/.test(file)) continue;
|
||||
let actual;
|
||||
try {
|
||||
actual = measureWorkflow(readFileSync(join(workflowDir, file), 'utf8'));
|
||||
} catch (error) {
|
||||
throw new Error(`${file}: ${error.message}`);
|
||||
}
|
||||
if (!actual) continue;
|
||||
const recorded = baseline.get(file);
|
||||
const entry = `${actual.join(' ')} ${file}`;
|
||||
if (!recorded) {
|
||||
errors.push(`${file}: missing budget; add '${entry}' to .ci-budget`);
|
||||
continue;
|
||||
}
|
||||
const growth = labels.flatMap((label, index) =>
|
||||
actual[index] > recorded[index]
|
||||
? [`${label} ${recorded[index]} -> ${actual[index]}`]
|
||||
: [],
|
||||
);
|
||||
if (growth.length) {
|
||||
errors.push(
|
||||
`${file}: ${growth.join(', ')}; update .ci-budget to '${entry}' if intentional`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (
|
||||
process.argv[1] &&
|
||||
import.meta.url === pathToFileURL(process.argv[1]).href
|
||||
) {
|
||||
try {
|
||||
const errors = checkBudget();
|
||||
for (const error of errors) console.error(`::error::${error}`);
|
||||
if (!errors.length) console.log('PR-stage CI budget holds');
|
||||
process.exitCode = errors.length ? 1 : 0;
|
||||
} catch (error) {
|
||||
console.error(`::error::${error.message}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
81
.github/scripts/check-ci-budget.test.mjs
vendored
Normal file
81
.github/scripts/check-ci-budget.test.mjs
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { checkBudget, measureWorkflow } from './check-ci-budget.mjs';
|
||||
|
||||
const workflow = `on:
|
||||
pull_request:
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- run: echo ok
|
||||
timeout-minutes: 3
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
`;
|
||||
|
||||
test('measures PR triggers and job-level timeouts, excluding scheduled workflows', () => {
|
||||
for (const trigger of [
|
||||
'pull_request',
|
||||
'pull_request_target',
|
||||
'merge_group',
|
||||
]) {
|
||||
assert.deepEqual(
|
||||
measureWorkflow(workflow.replace('pull_request', trigger)),
|
||||
[2, 10, 1],
|
||||
);
|
||||
}
|
||||
assert.deepEqual(
|
||||
measureWorkflow(
|
||||
workflow
|
||||
.replace('pull_request:', "'pull_request' :")
|
||||
.replace('timeout-minutes: 10', '"timeout-minutes" : 10'),
|
||||
),
|
||||
[2, 10, 1],
|
||||
);
|
||||
assert.equal(
|
||||
measureWorkflow(workflow.replace('pull_request', 'schedule')),
|
||||
null,
|
||||
);
|
||||
assert.throws(
|
||||
() => measureWorkflow('on: [pull_request]\njobs: {}'),
|
||||
/block-style/,
|
||||
);
|
||||
assert.throws(() => measureWorkflow('on:\n pull_request:\n'), /block-style/);
|
||||
assert.deepEqual(
|
||||
measureWorkflow(workflow.replace('10', '${{ inputs.timeout }}')),
|
||||
[2, 0, 2],
|
||||
);
|
||||
});
|
||||
|
||||
test('requires a ledger entry and enforces each budget dimension', (t) => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'ci-budget-'));
|
||||
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
||||
writeFileSync(join(dir, 'ci.yml'), workflow);
|
||||
const ledger = join(dir, '.ci-budget');
|
||||
assert.throws(() => checkBudget(dir), /ENOENT/);
|
||||
|
||||
for (const budget of ['2 10 1', '3 20 2']) {
|
||||
writeFileSync(ledger, `# budget\n${budget} ci.yml\n`);
|
||||
assert.deepEqual(checkBudget(dir), []);
|
||||
}
|
||||
for (const [budget, message] of [
|
||||
['1 10 1', 'jobs 1 -> 2'],
|
||||
['2 9 1', 'timeout-minutes 9 -> 10'],
|
||||
['2 10 0', 'jobs without fixed timeout 0 -> 1'],
|
||||
]) {
|
||||
writeFileSync(ledger, `${budget} ci.yml\n`);
|
||||
const errors = checkBudget(dir);
|
||||
assert.equal(errors.length, 1);
|
||||
assert.ok(errors[0].includes(message));
|
||||
}
|
||||
writeFileSync(ledger, '');
|
||||
assert.match(checkBudget(dir)[0], /missing budget/);
|
||||
for (const invalid of ['bad entry', '2 10 1 ci.yml\n2 10 1 ci.yml']) {
|
||||
writeFileSync(ledger, invalid);
|
||||
assert.throws(() => checkBudget(dir), /Invalid budget entry/);
|
||||
}
|
||||
});
|
||||
114
.github/scripts/ci-runner-routing.test.mjs
vendored
114
.github/scripts/ci-runner-routing.test.mjs
vendored
|
|
@ -69,6 +69,7 @@ function evalRunsOn(expression, { ecsDisabled, eventName, sameRepo, assoc }) {
|
|||
/github\.event_name == 'merge_group'/,
|
||||
String(eventName === 'merge_group'),
|
||||
],
|
||||
[/github\.event_name == 'push'/, String(eventName === 'push')],
|
||||
// Longest term first as a convention; both patterns are quote-anchored
|
||||
// (the closing quote is part of each regex), so neither can match
|
||||
// inside the other and the substitution order is behaviorally inert.
|
||||
|
|
@ -84,6 +85,10 @@ function evalRunsOn(expression, { ecsDisabled, eventName, sameRepo, assoc }) {
|
|||
/github\.event_name != 'pull_request'/,
|
||||
String(eventName !== 'pull_request'),
|
||||
],
|
||||
[
|
||||
/github\.event_name == 'pull_request'/,
|
||||
String(eventName === 'pull_request'),
|
||||
],
|
||||
[/github\.repository == 'QwenLM\/qwen-code'/, 'true'],
|
||||
[
|
||||
/github\.event\.pull_request\.head\.repo\.full_name == github\.repository/,
|
||||
|
|
@ -228,44 +233,21 @@ describe('ci.yml classify_pr runner routing', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('a push to main reaches ECS, and the kill-switch still wins', () => {
|
||||
// The post-merge fast signal (Classify PR + Test on `main`) runs the same
|
||||
// ten-plus-minute Test job a pull request does, in the most trusted
|
||||
// context there is: the code is already merged and the YAML is main's
|
||||
// own. Route it to the pool instead of spending a scarce hosted Linux
|
||||
// runner on every merge. A push carries no author association, so this
|
||||
// arm is necessarily event-based.
|
||||
assert.equal(
|
||||
runPickRunner({
|
||||
ecsDisabled: false,
|
||||
it('routes the push classifier and downstream jobs to the same pool', () => {
|
||||
for (const ecsDisabled of [false, true]) {
|
||||
const inputs = {
|
||||
ecsDisabled,
|
||||
sameRepo: false,
|
||||
assoc: '',
|
||||
eventName: 'push',
|
||||
}),
|
||||
ECS,
|
||||
);
|
||||
assert.equal(
|
||||
runPickRunner({
|
||||
ecsDisabled: true,
|
||||
sameRepo: false,
|
||||
assoc: '',
|
||||
eventName: 'push',
|
||||
}),
|
||||
HOSTED,
|
||||
'kill-switch must revert post-merge runs to hosted',
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps the push arm out of the classify runs-on expression', () => {
|
||||
// classify_pr routes twice, and on a push the two halves deliberately
|
||||
// disagree: the shell step sends downstream Linux jobs to ECS while this
|
||||
// expression leaves classify_pr itself hosted. Pin the asymmetry so a
|
||||
// future "drift fix" has to read why first — this expression is the
|
||||
// canonical association-routing text that sdk-java.yml and serve-ab.yml
|
||||
// mirror, a push has no association to route on, and classify_pr is a
|
||||
// seconds-long job whose pool costs nothing either way. The drift guard
|
||||
// above evaluates both halves on pull_request, where they must agree.
|
||||
assert.doesNotMatch(classifyRunsOn, /github\.event_name == 'push'/);
|
||||
};
|
||||
const expected = ecsDisabled ? HOSTED : ECS;
|
||||
assert.deepEqual(
|
||||
evalRunsOn(classifyRunsOn, inputs),
|
||||
JSON.parse(expected),
|
||||
);
|
||||
assert.equal(runPickRunner(inputs), expected);
|
||||
}
|
||||
});
|
||||
|
||||
it('the runs-on expression keeps the trusted clause and kill-switch', () => {
|
||||
|
|
@ -780,6 +762,68 @@ describe('trusted-PR lanes moved off the hosted queue (#13245)', () => {
|
|||
});
|
||||
}
|
||||
|
||||
it('release-managed-agent-reference.yml build: only trusted PR dry runs reach the pool', () => {
|
||||
const doc = parse(
|
||||
readFileSync(
|
||||
join(workflowsDir, 'release-managed-agent-reference.yml'),
|
||||
'utf8',
|
||||
),
|
||||
);
|
||||
const runsOn = String(doc.jobs.build['runs-on']);
|
||||
const route = (eventName, sameRepo, assoc, ecsDisabled = false) =>
|
||||
evalRunsOn(runsOn, { ecsDisabled, eventName, sameRepo, assoc });
|
||||
assert.deepEqual(route('pull_request', true, 'NONE'), ECS_LABELS);
|
||||
for (const assoc of TRUSTED) {
|
||||
assert.deepEqual(route('pull_request', false, assoc), ECS_LABELS, assoc);
|
||||
}
|
||||
for (const assoc of ['CONTRIBUTOR', 'FIRST_TIME_CONTRIBUTOR', 'NONE']) {
|
||||
assert.deepEqual(route('pull_request', false, assoc), HOSTED_LABELS);
|
||||
}
|
||||
// The dispatch build produces the published assets.
|
||||
assert.deepEqual(route('workflow_dispatch', true, ''), HOSTED_LABELS);
|
||||
assert.deepEqual(route('pull_request', true, 'OWNER', true), HOSTED_LABELS);
|
||||
assert.equal(doc.jobs.publish['runs-on'], 'ubuntu-latest');
|
||||
});
|
||||
|
||||
it('release-managed-agent-reference.yml build is safe on a shared pool host', () => {
|
||||
const text = readFileSync(
|
||||
join(workflowsDir, 'release-managed-agent-reference.yml'),
|
||||
'utf8',
|
||||
);
|
||||
const build = parse(text).jobs.build;
|
||||
// A service container cannot take a command, so it always reserves native
|
||||
// AIO contexts and can exhaust the pool host's fs.aio-max-nr (#14416).
|
||||
assert.equal(build.services, undefined);
|
||||
const start = build.steps.find(
|
||||
(s) => s.name === 'Start MySQL without native AIO',
|
||||
);
|
||||
assert.match(start.run, /--publish '127\.0\.0\.1::3306'/);
|
||||
assert.match(start.run, /type=tmpfs,destination=\/var\/lib\/mysql/);
|
||||
assert.match(start.run, /--innodb-use-native-aio=0/);
|
||||
assert.match(start.run, /MYSQL_PORT=.*GITHUB_ENV/);
|
||||
const remove = build.steps.find(
|
||||
(s) => s.name === 'Remove the MySQL container',
|
||||
);
|
||||
assert.match(remove.if, /always\(\)/);
|
||||
const run = (name) => build.steps.find((s) => s.name === name).run;
|
||||
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"/,
|
||||
);
|
||||
assert.match(
|
||||
run('Assemble reference distribution'),
|
||||
/rm -rf -- "\$\{RUNNER_TEMP\}\/managed-agent-reference"\n\s*node scripts\/package-managed-agent-reference\.mjs/,
|
||||
);
|
||||
const boot = run('Verify and boot the exact archive');
|
||||
assert.match(boot, /SERVER_PORT="\$\{server_port\}"/);
|
||||
assert.doesNotMatch(boot, /:8080|:3306\//);
|
||||
const setupNode = build.steps.find((s) =>
|
||||
String(s.uses || '').startsWith('actions/setup-node'),
|
||||
);
|
||||
assert.equal(setupNode.if, "${{ runner.environment == 'github-hosted' }}");
|
||||
});
|
||||
|
||||
it('tui-parity keeps setup-node and the pnpm cache off the pool', () => {
|
||||
// nodejs.org may be unreachable through the pool's egress proxy, and the
|
||||
// pool keeps its pnpm store on local disk — the ci.yml split.
|
||||
|
|
|
|||
12
.github/scripts/ci/main-failure-signature.mjs
vendored
12
.github/scripts/ci/main-failure-signature.mjs
vendored
|
|
@ -259,6 +259,18 @@ export function analyzeLogs(workflowName, logTexts, failedJobs = []) {
|
|||
}
|
||||
}
|
||||
|
||||
// Packaging and Cargo failures may report no test ID. Keep a standing
|
||||
// Desktop failure on one issue while preserving its platform and step.
|
||||
if (workflowName === 'Desktop Checks' && tests.length === 0) {
|
||||
const ids = new Set();
|
||||
for (const job of failedJobs) {
|
||||
for (const step of job.steps.length ? job.steps : ['']) {
|
||||
ids.add(`Desktop Checks > ${job.name}${step ? ` > ${step}` : ''}`);
|
||||
}
|
||||
}
|
||||
for (const id of ids) tests.push({ id, key: testKey(id) });
|
||||
}
|
||||
|
||||
// Guard-derived ids are the only run-level diagnoses in the set: the
|
||||
// collision they name is stable across every merge stacked on a standing
|
||||
// red, so they title the issue and are searched first. First-seen order
|
||||
|
|
|
|||
|
|
@ -764,6 +764,45 @@ test('falls back to a per-commit issue when no test can be identified', () => {
|
|||
assert.ok(!body.includes(OCCURRENCE_MARKER));
|
||||
});
|
||||
|
||||
test('deduplicates recurring Desktop packaging failures across commits', () => {
|
||||
const jobs = [{ name: 'Package / Build Windows x64', steps: ['Package'] }];
|
||||
const analysis = analyzeLogs('Desktop Checks', ['ParserError'], jobs);
|
||||
const first = renderIssueBody({ analysis, occurrence: OCCURRENCE });
|
||||
const next = { ...OCCURRENCE, sha: 'next-commit', runId: 'next-run' };
|
||||
const merged = renderIssueBody({
|
||||
analysis: analyzeLogs('Desktop Checks', ['ParserError'], jobs),
|
||||
occurrence: next,
|
||||
existingBody: first,
|
||||
});
|
||||
assert.equal(analysis.searchMarkers.length, 1);
|
||||
assert.ok(first.includes(analysis.searchMarkers[0]));
|
||||
assert.ok(merged.includes(OCCURRENCE.sha.slice(0, 12)));
|
||||
assert.ok(merged.includes(next.sha));
|
||||
assert.ok(!merged.includes(LEGACY_MARKER_PREFIX));
|
||||
});
|
||||
|
||||
test('keeps Desktop platforms and failed steps distinct', () => {
|
||||
const markers = [
|
||||
{ name: 'Package / Build Windows x64', steps: ['Package'] },
|
||||
{ name: 'Package / Build macOS arm64', steps: ['Package'] },
|
||||
{ name: 'Package / Build Windows x64', steps: ['Install'] },
|
||||
].map((job) => analyzeLogs('Desktop Checks', [], [job]).searchMarkers[0]);
|
||||
assert.equal(new Set(markers).size, 3);
|
||||
});
|
||||
|
||||
test('limits job-step fallback to Desktop failures without test IDs', () => {
|
||||
const jobs = [{ name: 'Desktop Shell (windows-2022)', steps: [] }];
|
||||
assert.equal(analyzeLogs('Desktop Checks', [], jobs).tests.length, 1);
|
||||
assert.equal(analyzeLogs('Desktop Checks', []).tests.length, 0);
|
||||
assert.equal(analyzeLogs('SDK Java', [], jobs).tests.length, 0);
|
||||
assert.deepEqual(
|
||||
analyzeLogs('Desktop Checks', [VITEST_LOG], jobs).tests.map(
|
||||
(entry) => entry.id,
|
||||
),
|
||||
[VITEST_TEST_ID],
|
||||
);
|
||||
});
|
||||
|
||||
test('the per-commit path leaves an already-filed body untouched', () => {
|
||||
const analysis = analyzeLogs('E2E Tests', ['npm error code ERESOLVE']);
|
||||
const existingBody = 'whatever the previous run wrote\n';
|
||||
|
|
|
|||
20
.github/workflows/.ci-budget
vendored
Normal file
20
.github/workflows/.ci-budget
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# PR-stage limits: jobs, declared timeout-minutes, jobs without fixed timeout, file.
|
||||
# Update the corresponding entry when intentionally increasing a budget.
|
||||
1 5 0 assign-pr-owner.yml
|
||||
8 180 5 ci.yml
|
||||
4 90 1 live-host-release.yml
|
||||
1 30 0 live-host.yml
|
||||
2 45 0 mobile-shell.yml
|
||||
1 20 0 pnpm-worktree-smoke.yml
|
||||
1 5 0 pr-force-push-reminder.yml
|
||||
1 5 0 pr-self-report-label.yml
|
||||
8 590 0 qwen-autofix.yml
|
||||
9 155 4 qwen-code-pr-review.yml
|
||||
7 280 2 qwen-triage.yml
|
||||
2 55 0 release-managed-agent-reference.yml
|
||||
2 0 2 sdk-python.yml
|
||||
1 60 0 serve-ab.yml
|
||||
2 60 0 tui-parity.yml
|
||||
3 70 0 verify-landlock-helper.yml
|
||||
1 5 0 web-shell-visuals-cleanup.yml
|
||||
1 30 0 web-shell-visuals.yml
|
||||
9
.github/workflows/.size-baseline
vendored
9
.github/workflows/.size-baseline
vendored
|
|
@ -22,8 +22,8 @@
|
|||
147136 ci.yml
|
||||
4193 codeql.yml
|
||||
9389 comment-attachment-guard.yml
|
||||
1634 desktop-packaging-check.yml
|
||||
38401 desktop-release.yml
|
||||
2090 desktop-packaging-check.yml
|
||||
44409 desktop-release.yml
|
||||
2038 docs-page-action.yml
|
||||
10005 dsw-swe-verified-release.yml
|
||||
40188 e2e.yml
|
||||
|
|
@ -48,7 +48,8 @@
|
|||
27648 qwen-triage-finalize.yml
|
||||
362377 qwen-triage.yml
|
||||
15171 release-chrome-extension.yml
|
||||
9701 release-sdk-java.yml
|
||||
22974 release-managed-agent-reference.yml
|
||||
11950 release-sdk-java.yml
|
||||
22345 release-sdk-python.yml
|
||||
20008 release-sdk.yml
|
||||
16655 release-vscode-companion.yml
|
||||
|
|
@ -57,7 +58,7 @@
|
|||
1079 scorecard-monthly.yml
|
||||
9751 sdk-java-contract-open-prs.yml
|
||||
9555 sdk-java-flyway-open-prs.yml
|
||||
60780 sdk-java.yml
|
||||
74834 sdk-java.yml
|
||||
3886 sdk-python.yml
|
||||
5595 security-checks.yml
|
||||
6777 serve-ab-publish.yml
|
||||
|
|
|
|||
14
.github/workflows/audio-capture-prebuilds.yml
vendored
14
.github/workflows/audio-capture-prebuilds.yml
vendored
|
|
@ -77,8 +77,20 @@ jobs:
|
|||
collect:
|
||||
name: 'collect prebuilds'
|
||||
needs: 'build'
|
||||
runs-on: 'ubuntu-latest'
|
||||
# Rides the same dedicated release-only machine as release.yml's publish
|
||||
# job, opted into via ECS_RELEASE_HOST_RUNNER_ENABLED. On that persistent
|
||||
# workspace a leftover prebuilds/ directory would leak into the merged
|
||||
# artifact (download-artifact merge-multiple merges into existing paths),
|
||||
# so the step below clears it first.
|
||||
runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.ECS_RELEASE_HOST_RUNNER_ENABLED == ''true'') && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen-release-host"]'') || fromJSON(''["ubuntu-latest"]'') }}'
|
||||
steps:
|
||||
- name: 'Clear stale prebuilds (self-hosted)'
|
||||
if: "${{ runner.environment == 'self-hosted' }}"
|
||||
# run steps resolve against defaults.run.working-directory
|
||||
# (packages/audio-capture), unlike the action inputs below which stay
|
||||
# workspace-relative.
|
||||
run: 'rm -rf prebuilds'
|
||||
|
||||
- name: 'Merge per-platform prebuilds'
|
||||
uses: 'actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c' # v8.0.1
|
||||
with:
|
||||
|
|
|
|||
211
.github/workflows/ci.yml
vendored
211
.github/workflows/ci.yml
vendored
|
|
@ -59,6 +59,18 @@ concurrency:
|
|||
# repo's namespace and PR runs DO cancel in progress, so a fork whose
|
||||
# default branch is `main` cancels a victim PR's run on every push.
|
||||
# Supersede within a PR is kept: one branch backs one open PR per fork.
|
||||
# `main` never cancels in progress, and that is intended rather than
|
||||
# accidental: a shared group holds at most one running plus one pending
|
||||
# run, and a newer pending run REPLACES the older one even with
|
||||
# cancel-in-progress: false, so back-to-back merges still coalesce onto the
|
||||
# newest tree — but the in-flight run always finishes and reports. Merges
|
||||
# land every ~18min (median) against this ~30min lane, and with no merge
|
||||
# queue this run is the only fast witness of a merged tree; cancelling it
|
||||
# starved the suite once already — e2e.yml measured 67 of 100 push runs
|
||||
# cancelled and only 25 ever reported (#5859). The coverage upload is
|
||||
# `always()`-gated, so a cancelled run would also publish partial coverage
|
||||
# under the complete run's artifact name. Release branches are treated the
|
||||
# same way.
|
||||
group: '${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.head_ref || github.ref }}'
|
||||
cancel-in-progress: |-
|
||||
${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/heads/release/') }}
|
||||
|
|
@ -87,8 +99,8 @@ env:
|
|||
# re-runs every suite after `npm ci`, and a push to main always classifies
|
||||
# `full` (classify_pr computes a profile only for pull_request), so what
|
||||
# the fast lane skips is still re-checked post-merge.
|
||||
HELPER_TESTS_DEP_FREE: '.github/scripts/pr-safety-precheck.test.mjs .github/scripts/cap-release-notes.test.mjs .github/scripts/ci/classify-profile.test.mjs .github/scripts/ci/classify-platform-sensitivity.test.mjs .github/scripts/ci/classify-pr-profile.test.mjs .github/scripts/upsert-bot-comment.test.mjs .github/scripts/ci/main-failure-signature.test.mjs .github/scripts/dsw-swe-verified/make-manifest.test.mjs .github/scripts/dsw-swe-verified/make-terminal-bench-manifest.test.mjs .github/scripts/update-ecs-runner-qwen-workflow.test.mjs .github/scripts/web-shell-visuals-compose.test.mjs .github/scripts/serve-ab-diff.test.mjs .github/scripts/serve-ab-drive.test.mjs .github/scripts/autofix-status-heartbeat.test.mjs .github/scripts/check-disk-floor.test.mjs .github/scripts/check-lint-gate-freshness.test.mjs .github/scripts/review-runner-schedule.test.mjs .github/scripts/ecs-runner/qwen-docker-cleanup.test.mjs .github/scripts/check-test-growth.test.mjs'
|
||||
HELPER_TESTS: '.github/scripts/pr-safety-precheck.test.mjs .github/scripts/cap-release-notes.test.mjs .github/scripts/ci/classify-profile.test.mjs .github/scripts/ci/classify-platform-sensitivity.test.mjs .github/scripts/ci/classify-pr-profile.test.mjs .github/scripts/upsert-bot-comment.test.mjs .github/scripts/ci/main-failure-signature.test.mjs .github/scripts/classify-release-notes.test.mjs .github/scripts/dsw-swe-verified/make-manifest.test.mjs .github/scripts/dsw-swe-verified/make-terminal-bench-manifest.test.mjs .github/scripts/resolve-sandbox-image.test.mjs .github/scripts/update-ecs-runner-qwen-workflow.test.mjs .github/scripts/web-shell-visuals-publish.test.mjs .github/scripts/web-shell-visuals-compose.test.mjs .github/scripts/serve-ab-diff.test.mjs .github/scripts/serve-ab-drive.test.mjs .github/scripts/qwen-triage-workflow.test.mjs .github/scripts/assign-issue-owner.test.mjs .github/scripts/auto-minimize-spam.test.mjs .github/scripts/ci-runner-routing.test.mjs .github/scripts/autofix-status-heartbeat.test.mjs .github/scripts/assign-pr-owner.test.mjs .github/scripts/check-disk-floor.test.mjs .github/scripts/check-lint-gate-freshness.test.mjs .github/scripts/ci-disk-pressure.test.mjs .github/scripts/e2e-build.test.mjs integration-tests/concurrent-runner/export-html-from-chatrecord-jsonl.test.mjs .github/scripts/review-runner-schedule.test.mjs .github/scripts/ecs-runner/qwen-docker-cleanup.test.mjs .github/scripts/ecs-runner/review-scratch-cleanup.test.mjs .github/scripts/check-test-growth.test.mjs'
|
||||
HELPER_TESTS_DEP_FREE: '.github/scripts/pr-safety-precheck.test.mjs .github/scripts/cap-release-notes.test.mjs .github/scripts/ci/classify-profile.test.mjs .github/scripts/ci/classify-platform-sensitivity.test.mjs .github/scripts/ci/classify-pr-profile.test.mjs .github/scripts/upsert-bot-comment.test.mjs .github/scripts/ci/main-failure-signature.test.mjs .github/scripts/dsw-swe-verified/make-manifest.test.mjs .github/scripts/dsw-swe-verified/make-terminal-bench-manifest.test.mjs .github/scripts/update-ecs-runner-qwen-workflow.test.mjs .github/scripts/web-shell-visuals-compose.test.mjs .github/scripts/serve-ab-diff.test.mjs .github/scripts/serve-ab-drive.test.mjs .github/scripts/autofix-status-heartbeat.test.mjs .github/scripts/check-disk-floor.test.mjs .github/scripts/check-lint-gate-freshness.test.mjs .github/scripts/review-runner-schedule.test.mjs .github/scripts/ecs-runner/qwen-docker-cleanup.test.mjs .github/scripts/check-test-growth.test.mjs .github/scripts/check-ci-budget.test.mjs'
|
||||
HELPER_TESTS: '.github/scripts/pr-safety-precheck.test.mjs .github/scripts/cap-release-notes.test.mjs .github/scripts/ci/classify-profile.test.mjs .github/scripts/ci/classify-platform-sensitivity.test.mjs .github/scripts/ci/classify-pr-profile.test.mjs .github/scripts/upsert-bot-comment.test.mjs .github/scripts/ci/main-failure-signature.test.mjs .github/scripts/classify-release-notes.test.mjs .github/scripts/dsw-swe-verified/make-manifest.test.mjs .github/scripts/dsw-swe-verified/make-terminal-bench-manifest.test.mjs .github/scripts/resolve-sandbox-image.test.mjs .github/scripts/update-ecs-runner-qwen-workflow.test.mjs .github/scripts/web-shell-visuals-publish.test.mjs .github/scripts/web-shell-visuals-compose.test.mjs .github/scripts/serve-ab-diff.test.mjs .github/scripts/serve-ab-drive.test.mjs .github/scripts/qwen-triage-workflow.test.mjs .github/scripts/assign-issue-owner.test.mjs .github/scripts/auto-minimize-spam.test.mjs .github/scripts/ci-runner-routing.test.mjs .github/scripts/autofix-status-heartbeat.test.mjs .github/scripts/assign-pr-owner.test.mjs .github/scripts/check-disk-floor.test.mjs .github/scripts/check-lint-gate-freshness.test.mjs .github/scripts/ci-disk-pressure.test.mjs .github/scripts/e2e-build.test.mjs integration-tests/concurrent-runner/export-html-from-chatrecord-jsonl.test.mjs .github/scripts/review-runner-schedule.test.mjs .github/scripts/ecs-runner/qwen-docker-cleanup.test.mjs .github/scripts/ecs-runner/review-scratch-cleanup.test.mjs .github/scripts/check-test-growth.test.mjs .github/scripts/check-ci-budget.test.mjs'
|
||||
# The growth ratchet and its vitest mirror compare each workflow against
|
||||
# the PR's base commit to tell "this PR grew the file" apart from "the
|
||||
# baseline went stale on main" (#9904). Wired once here so every lane
|
||||
|
|
@ -110,7 +122,7 @@ jobs:
|
|||
if: "${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch' || github.event_name == 'push' }}"
|
||||
# Gate runs on ECS for in-repo PRs, fork PRs whose author has write access
|
||||
# (OWNER/MEMBER/COLLABORATOR association — a write-access author is as
|
||||
# trusted as an in-repo branch), and the merge queue (base-repo context),
|
||||
# trusted as an in-repo branch), the merge queue, and pushes to main,
|
||||
# else a busy hosted pool delays it and blocks the ECS-bound jobs. The
|
||||
# kill-switch is read here, so flipping it reverts everything to hosted.
|
||||
# This runs-on and the pick_runner step below are the canonical home of
|
||||
|
|
@ -119,7 +131,7 @@ jobs:
|
|||
# not: a pull_request run executes the PR's own YAML, so no runs-on trust
|
||||
# clause is enforceable there and its lane never admits pull requests to
|
||||
# the pool.
|
||||
runs-on: '${{ (vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && (github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON(''["OWNER","MEMBER","COLLABORATOR"]''), github.event.pull_request.author_association) || github.event_name == ''merge_group'')) && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || fromJSON(''["ubuntu-latest"]'') }}'
|
||||
runs-on: '${{ (vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && (github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON(''["OWNER","MEMBER","COLLABORATOR"]''), github.event.pull_request.author_association) || github.event_name == ''merge_group'' || github.event_name == ''push'')) && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || fromJSON(''["ubuntu-latest"]'') }}'
|
||||
continue-on-error: true
|
||||
permissions:
|
||||
contents: 'read'
|
||||
|
|
@ -171,12 +183,8 @@ jobs:
|
|||
# PRs stay hosted. Disable via repo var
|
||||
# MAINTAINER_ECS_RUNNER_DISABLED=true.
|
||||
#
|
||||
# push is deliberately absent from the `runs-on` expression above, so a
|
||||
# post-merge run leaves classify_pr hosted while the Test job it feeds
|
||||
# goes to ECS. That is intentional: the expression is the canonical
|
||||
# association-routing text sdk-java.yml and serve-ab.yml mirror, a push
|
||||
# has no author association to route on, and classify_pr costs seconds —
|
||||
# while Test should not spend a scarce hosted runner on every merge.
|
||||
# Keep push routing aligned with the classifier above so a hosted queue
|
||||
# does not delay the downstream ECS jobs.
|
||||
- name: 'Select Linux runner'
|
||||
id: 'pick_runner'
|
||||
env:
|
||||
|
|
@ -889,6 +897,7 @@ jobs:
|
|||
timeout-minutes: '${{ fromJSON(contains(needs.classify_pr.outputs.ubuntu_runner || ''["ubuntu-latest"]'', ''ecs-qwen'') && ''90'' || ''45'') }}'
|
||||
permissions:
|
||||
contents: 'read'
|
||||
pull-requests: 'read'
|
||||
steps:
|
||||
# Pre-checkout cleanup: a previous containerised job (e.g. qwen-triage
|
||||
# verify) may leave root-owned, read-only files anywhere in the workspace.
|
||||
|
|
@ -1088,6 +1097,10 @@ jobs:
|
|||
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"
|
||||
run: '.github/scripts/check-workflow-size.sh'
|
||||
|
||||
- name: 'Check PR-stage CI budget'
|
||||
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"
|
||||
run: 'node .github/scripts/check-ci-budget.mjs'
|
||||
|
||||
# A green lane proves the branch passes the gate AS THE BRANCH defines
|
||||
# it — the checkout above is refs/pull/N/head by deliberate trade-off —
|
||||
# not as the base defines it. #11117 flipped Prettier --write →
|
||||
|
|
@ -1107,6 +1120,37 @@ jobs:
|
|||
HEAD_SHA: '${{ github.event.pull_request.head.sha }}'
|
||||
run: 'node .github/scripts/check-lint-gate-freshness.mjs'
|
||||
|
||||
# Keep the cheap schema guards on PRs and main while Java runs daily.
|
||||
- name: 'Check Flyway migration versions are unique'
|
||||
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile != 'docs_only' }}"
|
||||
run: 'node scripts/check-flyway-migrations.js packages/managed-agent-server packages/sdk-java/qwencode'
|
||||
|
||||
- name: 'Check the API contract version advances'
|
||||
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && github.event_name == 'pull_request' && steps.ci_profile.outputs.ci_profile != 'docs_only' }}"
|
||||
env:
|
||||
BASE_REF: '${{ github.event.pull_request.base.ref }}'
|
||||
GH_TOKEN: '${{ github.token }}'
|
||||
REPO: '${{ github.repository }}'
|
||||
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'
|
||||
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
|
||||
fi
|
||||
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')"
|
||||
if [ -n "${PREVIOUS_DOCUMENT}" ]; then
|
||||
git show "FETCH_HEAD:${PREVIOUS_DOCUMENT}" > "${BASE_DOCUMENT}"
|
||||
else
|
||||
: > "${BASE_DOCUMENT}"
|
||||
fi
|
||||
fi
|
||||
node scripts/check-contract-version.js "${BASE_DOCUMENT}" "${DOCUMENT}"
|
||||
|
||||
# Advisory test-growth summary and flag; see the script header.
|
||||
- name: 'Report test growth'
|
||||
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && github.event_name == 'pull_request' }}"
|
||||
|
|
@ -1257,18 +1301,21 @@ jobs:
|
|||
fi
|
||||
echo "Settings schema is up-to-date"
|
||||
|
||||
- name: 'Generate VS Code companion notices'
|
||||
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"
|
||||
run: 'npm run generate:notices --workspace=qwen-code-vscode-ide-companion'
|
||||
|
||||
- name: 'Check VS Code companion notices are up-to-date'
|
||||
- name: 'Generate Web Shell and VS Code companion notices'
|
||||
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"
|
||||
run: |-
|
||||
if [[ -n $(git status --porcelain packages/vscode-ide-companion/NOTICES.txt) ]]; then
|
||||
npm run generate:notices --workspace=@qwen-code/web-shell
|
||||
npm run generate:notices --workspace=qwen-code-vscode-ide-companion
|
||||
|
||||
- name: 'Check Web Shell and VS Code companion notices are up-to-date'
|
||||
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"
|
||||
run: |-
|
||||
if [[ -n $(git status --porcelain packages/vscode-ide-companion/NOTICES.txt packages/web-shell/NOTICES.txt) ]]; then
|
||||
echo "Error: NOTICES.txt is out of date."
|
||||
echo "Please run: npm run generate:notices --workspace=qwen-code-vscode-ide-companion"
|
||||
echo "And: npm run generate:notices --workspace=@qwen-code/web-shell"
|
||||
echo "Then commit the updated file."
|
||||
git diff --stat packages/vscode-ide-companion/NOTICES.txt
|
||||
git diff --stat packages/vscode-ide-companion/NOTICES.txt packages/web-shell/NOTICES.txt
|
||||
exit 1
|
||||
fi
|
||||
echo "NOTICES.txt is up-to-date"
|
||||
|
|
@ -1900,13 +1947,23 @@ jobs:
|
|||
( while true; do hosttests=$(pgrep -fc '[v]itest' 2>/dev/null || true); echo "DFSAMPLE $(date -u +%H:%M:%S 2>/dev/null) tmpdir[${TMPDIR}] load[$(cut -d' ' -f1-3 /proc/loadavg 2>/dev/null)] hosttests[${hosttests:-unknown}] space[$(df -h "${TMPDIR}" 2>/dev/null | tail -1)] inodes[$(df -i "${TMPDIR}" 2>/dev/null | tail -1)] memavail[$(awk '/MemAvailable/ {print $2, $3}' /proc/meminfo 2>/dev/null)]" 2>/dev/null; sleep 10; done ) &
|
||||
SAMPLER_PID=$!
|
||||
set +e
|
||||
npm run test:ci
|
||||
RC=$?
|
||||
npm run test:ci:workspaces
|
||||
WORKSPACE_RC=$?
|
||||
if [ "$WORKSPACE_RC" -ne 0 ]; then
|
||||
echo '::warning::The Windows workspace suite has known baseline failures; its full result remains visible above.'
|
||||
fi
|
||||
npm run test:scripts
|
||||
SCRIPTS_RC=$?
|
||||
if [ "$SCRIPTS_RC" -ne 0 ]; then
|
||||
echo '::warning::The complete Windows script suite has known baseline failures; its full result remains visible above.'
|
||||
fi
|
||||
npx vitest run scripts/tests/install-script.test.js --config scripts/tests/vitest.config.ts
|
||||
INSTALLER_RC=$?
|
||||
set -e
|
||||
pkill -TERM -P "$SAMPLER_PID" 2>/dev/null || true
|
||||
kill "$SAMPLER_PID" 2>/dev/null || true
|
||||
if [ "$RC" -ne 0 ]; then df -hT 2>/dev/null || df -h 2>/dev/null || true; df -i 2>/dev/null || true; grep -E 'MemTotal|MemAvailable|SwapTotal|SwapFree' /proc/meminfo 2>/dev/null || true; fi
|
||||
exit "$RC"
|
||||
if [ "$INSTALLER_RC" -ne 0 ]; then df -hT 2>/dev/null || df -h 2>/dev/null || true; df -i 2>/dev/null || true; grep -E 'MemTotal|MemAvailable|SwapTotal|SwapFree' /proc/meminfo 2>/dev/null || true; fi
|
||||
exit "$INSTALLER_RC"
|
||||
|
||||
# Unlike macOS, whose E2E legs run this file in full, no other Windows
|
||||
# lane runs the Hosted cases, so this one runs all of them and fails if
|
||||
|
|
@ -2161,6 +2218,8 @@ jobs:
|
|||
REQUESTY_API_KEY: ''
|
||||
XAI_API_KEY: ''
|
||||
ZAI_API_KEY: ''
|
||||
QWENCLOUD_API_KEY: ''
|
||||
QWENCLOUD_TOKEN_PLAN_API_KEY: ''
|
||||
QWEN_DEFAULT_AUTH_TYPE: ''
|
||||
run: |-
|
||||
mkdir -p "${HOME}" "${QWEN_HOME}"
|
||||
|
|
@ -2358,113 +2417,3 @@ jobs:
|
|||
- name: 'Run CLI Integration Tests'
|
||||
run: |-
|
||||
npm run test:integration:cli:sandbox:none
|
||||
|
||||
#
|
||||
# Desktop Shell: compile + test the Tauri crate in PR CI.
|
||||
#
|
||||
# The desktop-release workflow (workflow_dispatch only) is otherwise the sole
|
||||
# place this crate is built, so a compile error can land on a PR and stay
|
||||
# invisible until release time. This job compiles the crate and runs its
|
||||
# release-config tests on every PR that touches the shell. It does not need
|
||||
# the bundled runtime, so it is cheap. `cargo test` builds the crate and thus
|
||||
# catches compile failures (e.g. a moved-value error); fmt/clippy are not run
|
||||
# here because the release pipeline does not gate on them either.
|
||||
desktop_shell:
|
||||
name: 'Desktop Shell (${{ matrix.os }})'
|
||||
needs: 'classify_pr'
|
||||
if: "${{ !cancelled() && github.event_name != 'push' && github.event_name != 'schedule' && needs.classify_pr.outputs.skip_ci != 'true' }}"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: ['ubuntu-22.04', 'windows-2022']
|
||||
runs-on: '${{ matrix.os }}'
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
contents: 'read'
|
||||
pull-requests: 'read'
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
|
||||
with:
|
||||
ref: "${{ github.event.inputs.branch_ref || (github.event_name == 'pull_request' && format('refs/pull/{0}/head', github.event.pull_request.number)) || (github.event_name == 'merge_group' && github.event.merge_group.head_sha) || github.ref }}"
|
||||
fetch-depth: 1
|
||||
|
||||
# Fail open: any uncertainty (non-PR event, API failure) runs the job.
|
||||
#
|
||||
# Ask GitHub which files the PR changed, the same call the CI profile
|
||||
# classifier makes above. The two-tree `git diff BASE HEAD` this replaced
|
||||
# is not the PR's diff: it reports everything the base gained since the
|
||||
# branch point as a change on the PR's side. #8132 added
|
||||
# packages/desktop-shell (since renamed to packages/desktop), so every
|
||||
# branch older than it saw 78 phantom desktop paths, ran this job, and
|
||||
# failed in a checkout of refs/pull/N/head that has no such directory.
|
||||
# The rename cuts the other way and is not caught by the guard below: a
|
||||
# pre-rename head's files sit under packages/desktop-shell, which the
|
||||
# path filter no longer matches, so the job skips and reports green
|
||||
# having compiled nothing. Such a branch needs a rebase to be gated
|
||||
# again. The Cargo.toml guard covers the remaining case -- a head the
|
||||
# filter matched because ci.yml itself changed, with no crate at the
|
||||
# new path.
|
||||
- name: 'Detect desktop changes'
|
||||
id: 'filter'
|
||||
env:
|
||||
GH_TOKEN: '${{ github.token }}'
|
||||
PR_NUMBER: "${{ github.event_name == 'pull_request' && github.event.pull_request.number || '' }}"
|
||||
run: |-
|
||||
changed=true
|
||||
if [[ "${GITHUB_EVENT_NAME}" == "pull_request" && -n "${PR_NUMBER}" ]]; then
|
||||
# `previous_filename` too: renaming a file out of the crate changes
|
||||
# it, and only the old path says so.
|
||||
#
|
||||
# Both feed scripts are listed because `scripts/test-release.js`
|
||||
# is the only test either of them has, and it runs in this job.
|
||||
if files="$(gh api --paginate "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files" --jq '.[] | .filename, (.previous_filename // empty)')"; then
|
||||
if grep -Eq '^(packages/desktop/|\.github/scripts/create-desktop-update-manifest\.mjs|\.github/scripts/create-electron-bridge-manifest\.mjs|\.github/workflows/ci\.yml|\.github/workflows/desktop-release\.yml)' <<<"${files}"; then
|
||||
changed=true
|
||||
else
|
||||
changed=false
|
||||
fi
|
||||
else
|
||||
echo "::warning::Unable to list the PR's changed files; compiling the crate."
|
||||
fi
|
||||
fi
|
||||
# Whatever the filter decided, a tree without the crate cannot have
|
||||
# regressed it, and cargo has no manifest to run against — the job
|
||||
# would report a missing working directory as a failure of the PR.
|
||||
if [[ "${changed}" == "true" && ! -f packages/desktop/src-tauri/Cargo.toml ]]; then
|
||||
echo "::notice::packages/desktop/src-tauri is absent from this head; nothing to compile."
|
||||
changed=false
|
||||
fi
|
||||
echo "changed=${changed}" >> "${GITHUB_OUTPUT}"
|
||||
echo "desktop changed: ${changed}"
|
||||
|
||||
# cargo test links the Tauri/wry webview, so the WebKit/GTK dev headers
|
||||
# must be present (mirrors the Linux build job in desktop-release.yml).
|
||||
- name: 'Install Linux dependencies'
|
||||
if: "${{ steps.filter.outputs.changed == 'true' && runner.os == 'Linux' }}"
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libatk-bridge2.0-0 at-spi2-core dbus-x11 patchelf libfuse2 xdg-utils
|
||||
|
||||
- uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
|
||||
if: "${{ steps.filter.outputs.changed == 'true' && runner.os == 'Linux' }}"
|
||||
with:
|
||||
node-version: '22.x'
|
||||
|
||||
- uses: 'dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4' # stable
|
||||
if: "${{ steps.filter.outputs.changed == 'true' }}"
|
||||
|
||||
- uses: 'Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae' # v2
|
||||
if: "${{ steps.filter.outputs.changed == 'true' }}"
|
||||
with:
|
||||
workspaces: 'packages/desktop/src-tauri -> target'
|
||||
|
||||
- name: 'Compile and test the desktop crate'
|
||||
if: "${{ steps.filter.outputs.changed == 'true' }}"
|
||||
working-directory: 'packages/desktop'
|
||||
run: 'cargo test --manifest-path src-tauri/Cargo.toml'
|
||||
|
||||
- name: 'Run desktop release tests'
|
||||
if: "${{ steps.filter.outputs.changed == 'true' && runner.os == 'Linux' }}"
|
||||
working-directory: 'packages/desktop'
|
||||
run: 'node scripts/test-release.js'
|
||||
|
|
|
|||
13
.github/workflows/codeql.yml
vendored
13
.github/workflows/codeql.yml
vendored
|
|
@ -22,7 +22,7 @@ concurrency:
|
|||
|
||||
jobs:
|
||||
codeql:
|
||||
name: 'CodeQL (${{ matrix.language }})'
|
||||
name: 'CodeQL (${{ matrix.name || matrix.language }})'
|
||||
runs-on: 'ubuntu-latest'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
|
@ -33,7 +33,12 @@ jobs:
|
|||
- language: 'java'
|
||||
timeout: 15
|
||||
sourceroot: 'packages/sdk-java'
|
||||
# One job per language, each with its own measured cap: a shared job let
|
||||
- language: 'java'
|
||||
name: 'managed-agent-server'
|
||||
category: 'managed-agent-server'
|
||||
timeout: 15
|
||||
sourceroot: 'packages/managed-agent-server'
|
||||
# One job per source tree, each with its own measured cap: a shared job let
|
||||
# the JavaScript leg's overrun kill the Java analysis with it. The
|
||||
# JavaScript leg took 44m12s on the 2026-10-02 dispatch (run 37019120152)
|
||||
# after the old shared 30-minute cap had cancelled the nightly on
|
||||
|
|
@ -53,7 +58,7 @@ jobs:
|
|||
# snapshots, so a real build here would duplicate sdk-java.yml's
|
||||
# install dance. build-mode none extracts from source instead
|
||||
# (javascript is interpreted and never builds either way). The java
|
||||
# leg scans only the Maven modules: the Gradle tree under
|
||||
# legs scan only the Maven modules: the Gradle tree under
|
||||
# packages/mobile-shell needs a real build, its Kotlin sources are
|
||||
# out of scope, and its Gradle JDK recommendation must not set the
|
||||
# API model for the Java 21 Maven modules.
|
||||
|
|
@ -62,6 +67,8 @@ jobs:
|
|||
|
||||
- name: 'Perform CodeQL Analysis'
|
||||
uses: 'github/codeql-action/analyze@df559355d593797519d70b90fc8edd5db049e7a2' # ratchet:github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: '${{ matrix.category }}'
|
||||
|
||||
# The scan is schedule-only and never a required check, so a dead nightly is
|
||||
# otherwise silent (#13249). `always()` because an overrun leg reports
|
||||
|
|
|
|||
10
.github/workflows/desktop-packaging-check.yml
vendored
10
.github/workflows/desktop-packaging-check.yml
vendored
|
|
@ -1,14 +1,14 @@
|
|||
name: 'Desktop Packaging Check'
|
||||
name: 'Desktop Checks'
|
||||
|
||||
run-name: "Desktop packaging check (${{ inputs.qwen_code_ref || 'main' }})"
|
||||
run-name: "Desktop checks (${{ inputs.qwen_code_ref || 'main' }})"
|
||||
|
||||
# The desktop app bundles a Qwen Code runtime built from this repository's
|
||||
# `dist/`, so a change anywhere in the CLI can break desktop packaging. Until
|
||||
# this workflow existed, nothing exercised that path outside a manual release
|
||||
# dispatch: desktop release 0.2.3-preview.0 was the first run to see that a new
|
||||
# CLI dependency had shipped a native library the AppImage bundler chokes on,
|
||||
# two weeks after it landed. This runs the same jobs as a dry run so the
|
||||
# breakage surfaces on the day it lands instead of on release day.
|
||||
# two weeks after it landed. Daily dry runs exercise this path before release;
|
||||
# manual dispatch can validate a selected ref sooner.
|
||||
#
|
||||
# A dry run leaves the signing steps out (they are gated on `dry_run == false`),
|
||||
# so this does not cover macOS notarization or Windows Authenticode.
|
||||
|
|
@ -18,7 +18,7 @@ on:
|
|||
workflow_dispatch:
|
||||
inputs:
|
||||
qwen_code_ref:
|
||||
description: 'Qwen Code branch, tag, or commit to bundle.'
|
||||
description: 'Qwen Code branch, tag, or commit to bundle; it must contain the desktop crate (packages/desktop/src-tauri), or the run fails.'
|
||||
required: true
|
||||
default: 'main'
|
||||
type: 'string'
|
||||
|
|
|
|||
87
.github/workflows/desktop-release.yml
vendored
87
.github/workflows/desktop-release.yml
vendored
|
|
@ -12,7 +12,7 @@ on:
|
|||
required: true
|
||||
type: 'string'
|
||||
qwen_code_ref:
|
||||
description: 'Qwen Code branch or commit to bundle, reachable from main. A release tag whose own commit is off main is refused here: dispatch checks the ref itself, while only the release event peels to its parent.'
|
||||
description: 'Qwen Code branch or commit to bundle, reachable from main. Must contain the desktop crate (packages/desktop/src-tauri). A release tag whose own commit is off main is refused here: dispatch checks the ref itself, while only the release event peels to its parent.'
|
||||
required: true
|
||||
default: 'main'
|
||||
type: 'string'
|
||||
|
|
@ -225,10 +225,78 @@ jobs:
|
|||
fi
|
||||
echo "sha=$sha" >> "$GITHUB_OUTPUT"
|
||||
|
||||
desktop_shell:
|
||||
name: 'Desktop Shell (${{ matrix.os }})'
|
||||
needs: 'prepare'
|
||||
if: "${{ needs.prepare.outputs.already_published != 'true' }}"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: ['ubuntu-22.04', 'windows-2022']
|
||||
runs-on: '${{ matrix.os }}'
|
||||
timeout-minutes: 45
|
||||
defaults:
|
||||
run:
|
||||
shell: 'bash'
|
||||
permissions:
|
||||
contents: 'read'
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
|
||||
with:
|
||||
ref: '${{ needs.prepare.outputs.qwen_code_sha }}'
|
||||
fetch-depth: 1
|
||||
|
||||
- name: 'Check desktop crate'
|
||||
id: 'filter'
|
||||
run: |-
|
||||
changed=true
|
||||
if [[ ! -f packages/desktop/src-tauri/Cargo.toml ]]; then
|
||||
echo "::error::packages/desktop/src-tauri is absent from the release commit."
|
||||
exit 1
|
||||
fi
|
||||
echo "changed=${changed}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
# cargo test links the Tauri/wry webview, so the WebKit/GTK dev headers
|
||||
# must be present (mirrors the Linux build job in desktop-release.yml).
|
||||
- name: 'Install Linux dependencies'
|
||||
if: "${{ steps.filter.outputs.changed == 'true' && runner.os == 'Linux' }}"
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libatk-bridge2.0-0 at-spi2-core dbus-x11 patchelf libfuse2 xdg-utils
|
||||
|
||||
- uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
|
||||
if: "${{ steps.filter.outputs.changed == 'true' && runner.os == 'Linux' }}"
|
||||
with:
|
||||
node-version: '22.x'
|
||||
|
||||
- uses: 'dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4' # stable
|
||||
if: "${{ steps.filter.outputs.changed == 'true' }}"
|
||||
|
||||
- uses: 'Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae' # v2
|
||||
if: "${{ steps.filter.outputs.changed == 'true' }}"
|
||||
with:
|
||||
workspaces: 'packages/desktop/src-tauri -> target'
|
||||
|
||||
- name: 'Compile and test the desktop crate'
|
||||
if: "${{ steps.filter.outputs.changed == 'true' }}"
|
||||
working-directory: 'packages/desktop'
|
||||
run: 'cargo test --manifest-path src-tauri/Cargo.toml'
|
||||
|
||||
- name: 'Run desktop release tests'
|
||||
if: "${{ steps.filter.outputs.changed == 'true' && runner.os == 'Linux' }}"
|
||||
working-directory: 'packages/desktop'
|
||||
run: 'node scripts/test-release.js'
|
||||
|
||||
build:
|
||||
name: 'Build ${{ matrix.name }}'
|
||||
if: "${{ needs.prepare.outputs.already_published != 'true' }}"
|
||||
needs: 'prepare'
|
||||
# desktop_shell runs in parallel with build, not ahead of it: build
|
||||
# itself compiles and tests the same pinned revision on every matrix
|
||||
# entry, so serializing the shell lane would only add up to 45 minutes
|
||||
# before the build. publish joins on both, so nothing is released
|
||||
# unchecked.
|
||||
runs-on: '${{ matrix.os }}'
|
||||
timeout-minutes: 120
|
||||
strategy:
|
||||
|
|
@ -269,6 +337,8 @@ jobs:
|
|||
QWEN_DESKTOP_TARGET: '${{ matrix.rust_target }}'
|
||||
steps:
|
||||
- uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
|
||||
with:
|
||||
ref: '${{ needs.prepare.outputs.qwen_code_sha }}'
|
||||
- name: 'Configure source directory'
|
||||
shell: 'bash'
|
||||
run: 'echo "QWEN_CODE_ROOT=$RUNNER_TEMP/qwen-code-source" >> "$GITHUB_ENV"'
|
||||
|
|
@ -694,6 +764,7 @@ jobs:
|
|||
if: "${{ (github.event_name == 'release' || inputs.dry_run == false) && github.repository == 'QwenLM/qwen-code' && needs.prepare.outputs.already_published != 'true' }}"
|
||||
needs:
|
||||
- 'prepare'
|
||||
- 'desktop_shell'
|
||||
- 'build'
|
||||
runs-on: 'ubuntu-latest'
|
||||
timeout-minutes: 20
|
||||
|
|
@ -705,6 +776,8 @@ jobs:
|
|||
contents: 'write'
|
||||
steps:
|
||||
- uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
|
||||
with:
|
||||
ref: '${{ needs.prepare.outputs.qwen_code_sha }}'
|
||||
|
||||
- uses: 'actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c' # v8.0.1
|
||||
with:
|
||||
|
|
@ -793,9 +866,16 @@ jobs:
|
|||
ELECTRON_BRIDGE: '${{ inputs.electron_bridge }}'
|
||||
GH_TOKEN: '${{ github.token }}'
|
||||
FEED_TAG: '${{ env.DESKTOP_FEED_TAG }}'
|
||||
RELEASE_TAG: '${{ needs.prepare.outputs.tag }}'
|
||||
RELEASE_VERSION: '${{ needs.prepare.outputs.version }}'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# The README links here, so every stable publish rewrites the page title
|
||||
# and notes to name the current release rather than the assets below.
|
||||
title="Qwen Code Desktop latest (v$RELEASE_VERSION)"
|
||||
notes="$(printf '%s\n\n%s' \
|
||||
"Download Qwen Code Desktop v$RELEASE_VERSION for macOS, Windows, and Linux: https://github.com/$GITHUB_REPOSITORY/releases/tag/$RELEASE_TAG" \
|
||||
'This release is the stable auto-update feed. Files attached here serve the auto-updater; download installers from the link above.')"
|
||||
feed_assets=(release-assets/desktop-latest.json)
|
||||
if [ "$ELECTRON_BRIDGE" = 'true' ]; then
|
||||
shopt -s nullglob
|
||||
|
|
@ -838,8 +918,11 @@ jobs:
|
|||
exit 0
|
||||
fi
|
||||
gh release upload "$FEED_TAG" "${feed_assets[@]}" --clobber
|
||||
# Page text is cosmetic; it must not fail the job that gates the OSS mirror.
|
||||
gh release edit "$FEED_TAG" --title "$title" --notes "$notes" --latest=false ||
|
||||
echo "::warning::Could not update the $FEED_TAG release title and notes; the feed itself was published."
|
||||
else
|
||||
gh release create "$FEED_TAG" "${feed_assets[@]}" --title 'Qwen Code Desktop latest' --notes 'Stable desktop updater feed.' --latest=false
|
||||
gh release create "$FEED_TAG" "${feed_assets[@]}" --title "$title" --notes "$notes" --latest=false
|
||||
fi
|
||||
|
||||
- name: 'Publish release summary'
|
||||
|
|
|
|||
15
.github/workflows/finalize-release.yml
vendored
15
.github/workflows/finalize-release.yml
vendored
|
|
@ -263,9 +263,18 @@ jobs:
|
|||
set -euo pipefail
|
||||
gh pr review "${PR_URL}" --approve --body "Automated approval for the release version bump."
|
||||
|
||||
# Second approval uses CI_BOT_PAT which MUST be a different GitHub user
|
||||
# from both CI_REVIEW_BOT_PAT (PR author) and CI_DEV_BOT_PAT (first
|
||||
# approver). GitHub rejects self-approval at the API level. If this step
|
||||
# fails with "Can not approve your own pull request", rotate CI_BOT_PAT
|
||||
# to a distinct account. The step is non-blocking so that auto-merge can
|
||||
# still proceed when a human has already approved or when branch
|
||||
# protection only requires one approval. See #14341.
|
||||
- name: 'Approve release PR (second bot)'
|
||||
id: 'second_approve'
|
||||
if: |-
|
||||
${{ steps.meta.outputs.is_stable == 'true' && steps.pr.outputs.SHOULD_MERGE == 'true' }}
|
||||
continue-on-error: true
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ secrets.CI_BOT_PAT }}'
|
||||
PR_URL: '${{ steps.pr.outputs.PR_URL }}'
|
||||
|
|
@ -273,6 +282,12 @@ jobs:
|
|||
set -euo pipefail
|
||||
gh pr review "${PR_URL}" --approve --body "Automated second approval for the release version bump."
|
||||
|
||||
- name: 'Warn on second-bot approve failure'
|
||||
if: |-
|
||||
${{ steps.meta.outputs.is_stable == 'true' && steps.pr.outputs.SHOULD_MERGE == 'true' && steps.second_approve.outcome == 'failure' }}
|
||||
run: |-
|
||||
echo "::warning::Second-bot approve failed — CI_BOT_PAT may be the same GitHub user as the PR author (CI_REVIEW_BOT_PAT). Rotate CI_BOT_PAT to a distinct account. See https://github.com/QwenLM/qwen-code/issues/14341"
|
||||
|
||||
- name: 'Enable auto-merge for release PR'
|
||||
if: |-
|
||||
${{ steps.meta.outputs.is_stable == 'true' && steps.pr.outputs.SHOULD_MERGE == 'true' }}
|
||||
|
|
|
|||
28
.github/workflows/main-ci-failure-issue.yml
vendored
28
.github/workflows/main-ci-failure-issue.yml
vendored
|
|
@ -13,16 +13,14 @@ on:
|
|||
# to surface a regression as it lands; a failure there files an issue with
|
||||
# `type/bug`, `status/ready-for-agent` and `autofix/approved`, so expect
|
||||
# one per red merge, not per red nightly only.
|
||||
# 'SDK Java' is on the list for its post-merge push run. Its path filter
|
||||
# watches the SDK's embedding surfaces too (packages/cli/src/serve/**,
|
||||
# packages/acp-bridge/**, packages/cli/src/acp-integration/**), so it
|
||||
# fires on roughly half of recent merges to `main`, not only on SDK
|
||||
# changes — and that run is the only signal when a merge result is broken
|
||||
# in a way neither PR could see: two green branches whose Flyway
|
||||
# migrations claim one version collide only after both land (#12940: main
|
||||
# red for two hours while three more merges stacked on it). It has no
|
||||
# scheduled run, so the push arm covers it.
|
||||
workflows: ['E2E Tests', 'SDK Java', 'SDK Python', 'Qwen Code CI']
|
||||
# Daily Java and Desktop checks report main regressions through the same
|
||||
# deduplicated issue and autofix route as the nightly CLI checks.
|
||||
workflows:
|
||||
- 'E2E Tests'
|
||||
- 'SDK Java'
|
||||
- 'SDK Python'
|
||||
- 'Qwen Code CI'
|
||||
- 'Desktop Checks'
|
||||
types: ['completed']
|
||||
# Trigger-level branch filter, not only the `if` below: 'Qwen Code CI'
|
||||
# completes on every pull request, and without this each of those would
|
||||
|
|
@ -42,7 +40,13 @@ jobs:
|
|||
# job as outputs.
|
||||
analyze:
|
||||
name: 'Identify the failing tests'
|
||||
if: "${{ github.repository == 'QwenLM/qwen-code' && github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.head_branch == 'main' && (github.event.workflow_run.event == 'push' || (github.event.workflow_run.event == 'schedule' && github.event.workflow_run.name == 'Qwen Code CI')) }}"
|
||||
if: >-
|
||||
${{ github.repository == 'QwenLM/qwen-code' &&
|
||||
github.event.workflow_run.conclusion == 'failure' &&
|
||||
github.event.workflow_run.head_branch == 'main' &&
|
||||
(github.event.workflow_run.event == 'push' ||
|
||||
(github.event.workflow_run.event == 'schedule' &&
|
||||
contains(fromJSON('["Qwen Code CI","SDK Java","Desktop Checks"]'), github.event.workflow.name))) }}
|
||||
runs-on: 'ubuntu-latest'
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
|
|
@ -172,7 +176,7 @@ jobs:
|
|||
env:
|
||||
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
REPO: '${{ github.repository }}'
|
||||
WORKFLOW_NAME: '${{ github.event.workflow_run.name }}'
|
||||
WORKFLOW_NAME: '${{ github.event.workflow.name }}'
|
||||
WORKFLOW_RUN_ID: '${{ github.event.workflow_run.id }}'
|
||||
WORKFLOW_RUN_URL: '${{ github.event.workflow_run.html_url }}'
|
||||
# Actions supplies the timestamp; the helper stays free of clock reads
|
||||
|
|
|
|||
14
.github/workflows/qwen-triage.yml
vendored
14
.github/workflows/qwen-triage.yml
vendored
|
|
@ -2235,7 +2235,7 @@ jobs:
|
|||
# pin, upload, cleanup ~5m
|
||||
# java toolchain + Maven repo
|
||||
# + sibling modules ≤ ~20m, and only when the diff touches
|
||||
# packages/sdk-java/. curl --max-time caps
|
||||
# a Java module. curl --max-time caps
|
||||
# each download; `timeout 300` caps each
|
||||
# mvn install
|
||||
# ------------------------------------
|
||||
|
|
@ -2365,10 +2365,10 @@ jobs:
|
|||
exit 0
|
||||
fi
|
||||
# Same full list, same `grep <<<` (a pipe would SIGPIPE under
|
||||
# pipefail). Only packages/sdk-java/ pays for a JDK; the broader
|
||||
# pipefail). Only the Maven package trees pay for a JDK; the broader
|
||||
# sdk-java.yml path list is mostly core/CLI, and those diffs still
|
||||
# cannot run the database-backed Java ITs this lane does not host.
|
||||
if grep -qE '^packages/sdk-java/' <<< "$files"; then
|
||||
if grep -qE '^packages/(sdk-java|managed-agent-server)/' <<< "$files"; then
|
||||
echo "java=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
# Snapshot PR metadata for the token-free agent: the agent env
|
||||
|
|
@ -2698,7 +2698,7 @@ jobs:
|
|||
echo "Java toolchain ready at ${prefix} (Temurin ${TEMURIN_VERSION}, Maven ${MAVEN_VERSION})."
|
||||
else
|
||||
rm -rf -- "$prefix"
|
||||
echo "::warning::Java toolchain install failed; sdk-java changes will be reported as not covered."
|
||||
echo "::warning::Java toolchain install failed; Java changes will be reported as not covered."
|
||||
fi
|
||||
rm -rf -- "$work"
|
||||
|
||||
|
|
@ -3219,7 +3219,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') }}"
|
||||
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'
|
||||
|
|
@ -4096,10 +4096,8 @@ jobs:
|
|||
{
|
||||
run_mvn packages/sdk-java/qwencode/pom.xml -DskipTests -Dgpg.skip=true -Dmaven.javadoc.skip=true install
|
||||
qwencode_status=$?
|
||||
run_mvn packages/sdk-java/runtime-broker/pom.xml -DskipTests -Dspotbugs.skip=true install
|
||||
broker_status=$?
|
||||
} >> "$log" 2>&1
|
||||
printf 'qwencode=%s runtime-broker=%s\n' "$qwencode_status" "$broker_status" >> "$log"
|
||||
printf 'qwencode=%s\n' "$qwencode_status" >> "$log"
|
||||
|
||||
- name: 'Install evidence browser'
|
||||
if: "steps.pr.outputs.decision == 'run' && steps.prepare.outputs.verdict == ''"
|
||||
|
|
|
|||
486
.github/workflows/release-managed-agent-reference.yml
vendored
Normal file
486
.github/workflows/release-managed-agent-reference.yml
vendored
Normal file
|
|
@ -0,0 +1,486 @@
|
|||
name: 'Managed Agent Reference Distribution'
|
||||
|
||||
run-name: "Managed Agent reference ${{ inputs.version || format('PR #{0} dry run', github.event.pull_request.number) }}"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/release-managed-agent-reference.yml'
|
||||
- 'scripts/package-managed-agent-reference.mjs'
|
||||
- 'scripts/contract-version-utils.js'
|
||||
- 'scripts/flyway-migration-utils.js'
|
||||
- 'scripts/tests/package-managed-agent-reference.test.mjs'
|
||||
- 'packages/sdk-java/qwencode/**'
|
||||
- 'packages/managed-agent-server/**'
|
||||
- 'packages/core/src/managed-runtime/contracts/**'
|
||||
- 'packages/cli/src/serve/capabilities.ts'
|
||||
- 'packages/cli/src/serve/broker-managed-runtime-provider.ts'
|
||||
- 'packages/cli/src/serve/hosted-harness-contract.ts'
|
||||
- 'docs/users/managed-agent-reference-deployment.md'
|
||||
- 'docs/users/managed-agent-reference-deployment.zh-CN.md'
|
||||
- 'docs/users/hosted-workspace-recovery.md'
|
||||
- 'docs/users/hosted-workspace-recovery.zh-CN.md'
|
||||
- 'LICENSE'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Experimental distribution version, for example 0.1.0-alpha.1.'
|
||||
required: true
|
||||
type: 'string'
|
||||
dry_run:
|
||||
description: 'Build and verify without creating a tag or GitHub prerelease.'
|
||||
required: true
|
||||
default: true
|
||||
type: 'boolean'
|
||||
|
||||
permissions:
|
||||
contents: 'read'
|
||||
|
||||
concurrency:
|
||||
group: 'managed-agent-reference-${{ github.event.pull_request.number || github.run_id }}'
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: 'Build and verify reference distribution'
|
||||
# Only trusted PR dry runs use the persistent pool. A dispatch build feeds
|
||||
# the published release, so it stays on a fresh hosted VM that never runs
|
||||
# PR code.
|
||||
runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && github.event_name == ''pull_request'' && (github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON(''["OWNER","MEMBER","COLLABORATOR"]''), github.event.pull_request.author_association))) && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || fromJSON(''["ubuntu-latest"]'') }}'
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
MAVEN_VERSION: '3.9.11'
|
||||
MAVEN_SHA512: 'bcfe4fe305c962ace56ac7b5fc7a08b87d5abd8b7e89027ab251069faebee516b0ded8961445d6d91ec1985dfe30f8153268843c89aa392733d1a3ec956c9978'
|
||||
MYSQL_CONTAINER: 'qwen-code-reference-mysql-${{ github.run_id }}-${{ github.run_attempt }}'
|
||||
outputs:
|
||||
tag: '${{ steps.version.outputs.tag }}'
|
||||
version: '${{ steps.version.outputs.version }}'
|
||||
steps:
|
||||
- name: 'Resolve experimental version'
|
||||
id: 'version'
|
||||
shell: 'bash'
|
||||
env:
|
||||
INPUT_VERSION: '${{ inputs.version }}'
|
||||
PR_NUMBER: '${{ github.event.pull_request.number }}'
|
||||
DRY_RUN: '${{ inputs.dry_run }}'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
if [[ "${GITHUB_EVENT_NAME}" == 'pull_request' ]]; then
|
||||
version="0.0.0-pr.${PR_NUMBER}"
|
||||
else
|
||||
version="${INPUT_VERSION#v}"
|
||||
fi
|
||||
invalid_version() {
|
||||
echo '::error::The reference distribution requires an experimental SemVer with a prerelease.'
|
||||
exit 1
|
||||
}
|
||||
if [[ ! "${version}" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$ ]]; then
|
||||
invalid_version
|
||||
fi
|
||||
prerelease="${version#*-}"
|
||||
IFS='.' read -r -a identifiers <<< "${prerelease}"
|
||||
for identifier in "${identifiers[@]}"; do
|
||||
if [[ "${identifier}" =~ ^[0-9]+$ && "${#identifier}" -gt 1 && "${identifier:0:1}" == '0' ]]; then
|
||||
invalid_version
|
||||
fi
|
||||
done
|
||||
if [[ "${GITHUB_EVENT_NAME}" == 'workflow_dispatch' && "${DRY_RUN}" == 'false' && "${GITHUB_REF}" != 'refs/heads/main' ]]; then
|
||||
echo '::error::Published reference distributions must run from main.'
|
||||
exit 1
|
||||
fi
|
||||
echo "version=${version}" >> "${GITHUB_OUTPUT}"
|
||||
echo "tag=managed-agent-reference-v${version}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: 'Restore workspace ownership'
|
||||
if: "${{ runner.environment == 'self-hosted' }}"
|
||||
run: |-
|
||||
set -uo pipefail
|
||||
RUNNER_UID="$(id -u)"
|
||||
RUNNER_GID="$(id -g)"
|
||||
if [ "$RUNNER_UID" != "0" ]; then
|
||||
chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace ownership; checkout may fail on leftover root-owned files"
|
||||
fi
|
||||
chmod -R u+rwX "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chmod -R u+rwX "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace write permissions; checkout may fail on leftover read-only files"
|
||||
# A leftover workspace owned by a different uid than the step user
|
||||
# (e.g. a runner-user checkout under root-run steps) makes every git
|
||||
# call fail with "detected dubious ownership". The pinned
|
||||
# actions/checkout adds safe.directory only to its own temporary
|
||||
# global config, so later steps are uncovered; trust the workspace
|
||||
# here instead. The --get-all guard keeps a persistent runner's
|
||||
# gitconfig from accumulating a duplicate entry per job.
|
||||
git config --global --get-all safe.directory 2>/dev/null | grep -qxF "$GITHUB_WORKSPACE" || git config --global --add safe.directory "$GITHUB_WORKSPACE" || echo "::warning::could not mark the workspace as a git safe.directory; git steps may fail on a mismatched-ownership checkout"
|
||||
|
||||
# ci.yml's pre-checkout sweep, byte-identical to the ci.yml copies
|
||||
# (pinned in scripts/tests/sdk-java-workflow.test.js): residue left by
|
||||
# review/autofix jobs on the shared ECS pool fails actions/checkout's
|
||||
# clean — the #13506 failure. Hosted legs skip it on fresh VMs.
|
||||
- name: 'Clean stale .qwen before checkout'
|
||||
if: "${{ runner.environment == 'self-hosted' }}"
|
||||
run: |-
|
||||
set -uo pipefail
|
||||
for stale_qwen in "$GITHUB_WORKSPACE/.qwen" "$GITHUB_WORKSPACE/.qwen.root-orig" "$GITHUB_WORKSPACE/trusted-ci-classifier"; do
|
||||
if [ ! -e "$stale_qwen" ] && [ ! -L "$stale_qwen" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ -d "$stale_qwen" ] && [ ! -L "$stale_qwen" ]; then
|
||||
chmod -R u+w "$stale_qwen" 2>/dev/null || true
|
||||
fi
|
||||
# A foreign-owned directory cannot always be renamed to a
|
||||
# different parent: updating its .. entry can fail even when the
|
||||
# workspace parent is writable. If that individual move fails,
|
||||
# quarantine the runner-owned workspace itself, then recreate the
|
||||
# empty checkout root. Warm contents are lost only on this
|
||||
# otherwise unrecoverable path.
|
||||
rm -rf -- "$stale_qwen" 2>/dev/null ||
|
||||
sudo -n rm -rf -- "$stale_qwen" 2>/dev/null ||
|
||||
{
|
||||
quarantine="$(dirname -- "$GITHUB_WORKSPACE")/_qwen-quarantine"
|
||||
mkdir -p "$quarantine" 2>/dev/null || true
|
||||
stale_name="$(basename -- "$stale_qwen")"
|
||||
if mv -- "$stale_qwen" "$quarantine/${stale_name#\.}-$(date -u +%Y%m%dT%H%M%SZ)-$$" 2>/dev/null; then
|
||||
echo "::warning::could not delete leaked $stale_name; moved it to $quarantine so this checkout can proceed — that directory needs manual cleanup"
|
||||
else
|
||||
workspace_quarantine="$quarantine/workspace-$(date -u +%Y%m%dT%H%M%SZ)-$$"
|
||||
if mv -- "$GITHUB_WORKSPACE" "$workspace_quarantine" 2>/dev/null &&
|
||||
mkdir -p "$GITHUB_WORKSPACE" 2>/dev/null &&
|
||||
cd "$GITHUB_WORKSPACE"; then
|
||||
echo "::warning::could not delete leaked $stale_name; moved the whole workspace to $workspace_quarantine so this checkout can proceed — that directory needs manual cleanup"
|
||||
break
|
||||
else
|
||||
echo "::warning::leaked $stale_name survived every recovery; runner needs manual cleanup"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
done
|
||||
# Interrupted reviews leave worktree registrations under .qwen/tmp/
|
||||
# and qwen-review/* branches behind. prune drops registrations whose
|
||||
# directories the rm above removed; worktree remove --force then
|
||||
# clears any still-registered leftover directory (--force tolerates
|
||||
# dirty contents), since a branch checked out in a live worktree
|
||||
# cannot be deleted. If removal still fails, the registration
|
||||
# survives and the branch delete below warns. The sweep deletes all
|
||||
# review artifacts, not just the current PR's: safe because a runner
|
||||
# executes one job at a time. Kept inline rather than a shared
|
||||
# script: this runs pre-checkout on shared runners, where leftover
|
||||
# workspace files are untrusted.
|
||||
if [ -e "$GITHUB_WORKSPACE/.git" ]; then
|
||||
GIT_SAFE=(git -c core.hooksPath=/dev/null -c core.fsmonitor= -C "$GITHUB_WORKSPACE")
|
||||
"${GIT_SAFE[@]}" worktree prune -v || true
|
||||
"${GIT_SAFE[@]}" worktree list --porcelain \
|
||||
| awk '$1 == "worktree" && index($0, "/.qwen/tmp/review-pr-") > 0 { sub(/^worktree /, ""); print }' \
|
||||
| while read -r worktree; do
|
||||
[ -n "$worktree" ] || continue
|
||||
# Registered paths come from leftover git metadata and are
|
||||
# untrusted: the awk filter above matched by substring, so reject
|
||||
# `..` traversal and re-anchor to the review prefix before the
|
||||
# destructive remove.
|
||||
case "$worktree" in
|
||||
*/../*|../*|*/..)
|
||||
echo "::warning::skipping suspicious review worktree path: $worktree"
|
||||
continue
|
||||
;;
|
||||
"$GITHUB_WORKSPACE/.qwen/tmp/review-pr-"*) : ;;
|
||||
*)
|
||||
echo "::warning::skipping unexpected review worktree path: $worktree"
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
"${GIT_SAFE[@]}" worktree remove --force "$worktree" ||
|
||||
echo "::warning::could not remove review worktree: $worktree"
|
||||
done || true
|
||||
"${GIT_SAFE[@]}" worktree prune -v || true
|
||||
"${GIT_SAFE[@]}" for-each-ref --format='%(refname:short)' 'refs/heads/qwen-review/*' \
|
||||
| while read -r stale_ref; do
|
||||
if [ -n "$stale_ref" ]; then
|
||||
"${GIT_SAFE[@]}" branch -D "$stale_ref" ||
|
||||
echo "::warning::could not remove review branch: $stale_ref"
|
||||
fi
|
||||
done || true
|
||||
fi
|
||||
|
||||
- name: 'Recreate runner Git metadata'
|
||||
if: "${{ runner.environment == 'self-hosted' }}"
|
||||
run: |-
|
||||
rm -rf -- "$GITHUB_WORKSPACE/.git"
|
||||
|
||||
- name: 'Checkout source revision'
|
||||
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: 'Set up Java 21'
|
||||
uses: 'actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654' # v5.2.0
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
cache: "${{ runner.environment == 'github-hosted' && 'maven' || '' }}"
|
||||
cache-dependency-path: |-
|
||||
packages/sdk-java/qwencode/pom.xml
|
||||
packages/managed-agent-server/pom.xml
|
||||
settings-path: '${{ runner.temp }}/setup-java-m2'
|
||||
|
||||
- name: 'Set up Maven (self-hosted)'
|
||||
if: "${{ runner.environment == 'self-hosted' }}"
|
||||
run: |-
|
||||
archive="${RUNNER_TEMP}/apache-maven-${MAVEN_VERSION}-bin.tar.gz"
|
||||
curl -fsSL "https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/${MAVEN_VERSION}/apache-maven-${MAVEN_VERSION}-bin.tar.gz" -o "${archive}"
|
||||
echo "${MAVEN_SHA512} ${archive}" | sha512sum --check --status
|
||||
tar -xzf "${archive}" -C "${RUNNER_TEMP}"
|
||||
echo "${RUNNER_TEMP}/apache-maven-${MAVEN_VERSION}/bin" >> "${GITHUB_PATH}"
|
||||
|
||||
- name: 'Set up Node.js 22 (hosted)'
|
||||
if: "${{ runner.environment == 'github-hosted' }}"
|
||||
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
|
||||
with:
|
||||
node-version: '22'
|
||||
package-manager-cache: false
|
||||
|
||||
- name: 'Use pre-installed Node.js (self-hosted)'
|
||||
if: "${{ runner.environment == 'self-hosted' }}"
|
||||
uses: './.github/actions/self-hosted-node'
|
||||
|
||||
- name: 'Check pool disk floor'
|
||||
if: "${{ runner.environment == 'self-hosted' }}"
|
||||
run: 'bash .github/scripts/check-disk-floor.sh "${GITHUB_WORKSPACE}" "${RUNNER_TEMP:-/tmp}"'
|
||||
|
||||
# Pool hosts share one host-wide fs.aio-nr budget across concurrent jobs,
|
||||
# and a mysqld that reserves native AIO contexts can exhaust it and fail
|
||||
# to boot with `io_setup() failed with EAGAIN` (#14416). Service
|
||||
# containers cannot take a command, so MySQL starts here instead, with
|
||||
# native AIO disabled: simulated AIO needs no io_setup() contexts.
|
||||
- name: 'Start MySQL without native AIO'
|
||||
shell: 'bash'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
docker run --detach --name "${MYSQL_CONTAINER}" \
|
||||
--publish '127.0.0.1::3306' \
|
||||
--mount type=tmpfs,destination=/var/lib/mysql \
|
||||
--env MYSQL_DATABASE='qwen_managed_agent' \
|
||||
--env MYSQL_PASSWORD='qwen' \
|
||||
--env MYSQL_ROOT_PASSWORD='root' \
|
||||
--env MYSQL_USER='qwen' \
|
||||
mysql:8.4 --innodb-use-native-aio=0
|
||||
for attempt in {1..60}; do
|
||||
if docker exec "${MYSQL_CONTAINER}" mysqladmin ping --host=127.0.0.1 --user=root --password=root --silent >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
if ! docker inspect --format '{{.State.Running}}' "${MYSQL_CONTAINER}" | grep -qx true; then
|
||||
docker logs "${MYSQL_CONTAINER}" || true
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${attempt}" == '60' ]]; then
|
||||
docker logs "${MYSQL_CONTAINER}" || true
|
||||
exit 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
MYSQL_PORT="$(docker inspect --format '{{(index (index .NetworkSettings.Ports "3306/tcp") 0).HostPort}}' "${MYSQL_CONTAINER}")"
|
||||
echo "MYSQL_PORT=${MYSQL_PORT}" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: 'Grant Flyway probe access'
|
||||
shell: 'bash'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
docker exec "${MYSQL_CONTAINER}" \
|
||||
mysql -uroot -proot \
|
||||
--execute="GRANT SELECT ON performance_schema.user_variables_by_thread TO 'qwen'@'%';"
|
||||
|
||||
- name: 'Test the packager'
|
||||
run: 'node --test scripts/tests/package-managed-agent-reference.test.mjs'
|
||||
|
||||
- name: 'Build Java components'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
export MAVEN_ARGS="--settings ${RUNNER_TEMP}/setup-java-m2/settings.xml --toolchains ${RUNNER_TEMP}/setup-java-m2/toolchains.xml"
|
||||
if [[ "${RUNNER_ENVIRONMENT}" == 'self-hosted' ]]; then
|
||||
# Both in-house artifacts use fixed release coordinates, so this
|
||||
# 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"
|
||||
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
|
||||
mvn --batch-mode --no-transfer-progress -f packages/managed-agent-server/pom.xml -DskipTests clean package
|
||||
|
||||
- name: 'Assemble reference distribution'
|
||||
env:
|
||||
RELEASE_VERSION: '${{ steps.version.outputs.version }}'
|
||||
run: |-
|
||||
# A pool runner can keep RUNNER_TEMP across jobs: a leftover
|
||||
# extracted/ fails the verify step's mkdir, and another PR's
|
||||
# archive would match the upload globs.
|
||||
rm -rf -- "${RUNNER_TEMP}/managed-agent-reference"
|
||||
node scripts/package-managed-agent-reference.mjs \
|
||||
--version "${RELEASE_VERSION}" \
|
||||
--source-revision "${GITHUB_SHA}" \
|
||||
--output "${RUNNER_TEMP}/managed-agent-reference"
|
||||
|
||||
- name: 'Verify and boot the exact archive'
|
||||
env:
|
||||
RELEASE_VERSION: '${{ steps.version.outputs.version }}'
|
||||
shell: 'bash'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
release="${RUNNER_TEMP}/managed-agent-reference"
|
||||
cd "${release}"
|
||||
sha256sum --check SHA256SUMS.txt
|
||||
mkdir extracted
|
||||
tar -xzf "qwen-managed-agent-reference-${RELEASE_VERSION}.tar.gz" -C extracted
|
||||
bundle="${release}/extracted/qwen-managed-agent-reference-${RELEASE_VERSION}"
|
||||
cd "${bundle}"
|
||||
sha256sum --check SHA256SUMS
|
||||
server_port="$(node -e "const s=require('net').createServer().listen(0,'127.0.0.1',()=>{console.log(s.address().port);s.close()})")"
|
||||
SERVER_PORT="${server_port}" \
|
||||
SPRING_DATASOURCE_URL="jdbc:mysql://127.0.0.1:${MYSQL_PORT}/qwen_managed_agent?allowPublicKeyRetrieval=true&useSSL=false" \
|
||||
SPRING_DATASOURCE_USERNAME='qwen' \
|
||||
SPRING_DATASOURCE_PASSWORD='qwen' \
|
||||
bin/qwen-managed-agent-server > "${RUNNER_TEMP}/managed-agent-reference-boot.log" 2>&1 &
|
||||
server_pid=$!
|
||||
trap 'kill "${server_pid}" 2>/dev/null || true; wait "${server_pid}" 2>/dev/null || true' EXIT
|
||||
for attempt in {1..120}; do
|
||||
if curl --fail --silent --show-error "http://127.0.0.1:${server_port}/actuator/health" > "${RUNNER_TEMP}/managed-agent-reference-health.json"; then
|
||||
break
|
||||
fi
|
||||
if ! kill -0 "${server_pid}" 2>/dev/null; then
|
||||
cat "${RUNNER_TEMP}/managed-agent-reference-boot.log"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${attempt}" == '120' ]]; then
|
||||
cat "${RUNNER_TEMP}/managed-agent-reference-boot.log"
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
grep -q '"status":"UP"' "${RUNNER_TEMP}/managed-agent-reference-health.json"
|
||||
|
||||
- name: 'Upload verified release assets'
|
||||
uses: 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' # v7.0.1
|
||||
with:
|
||||
name: 'managed-agent-reference-${{ steps.version.outputs.version }}'
|
||||
path: |-
|
||||
${{ runner.temp }}/managed-agent-reference/*.tar.gz
|
||||
${{ runner.temp }}/managed-agent-reference/*.manifest.json
|
||||
${{ runner.temp }}/managed-agent-reference/SHA256SUMS.txt
|
||||
if-no-files-found: 'error'
|
||||
retention-days: 14
|
||||
|
||||
# The host's docker cleanup service reaps qwen-code-* containers only
|
||||
# after a 24h age gate, so a cancelled job would strand a running mysqld
|
||||
# on the shared pool for up to a day.
|
||||
- name: 'Remove the MySQL container'
|
||||
if: '${{ always() }}'
|
||||
shell: 'bash'
|
||||
run: |-
|
||||
set -uo pipefail
|
||||
if docker inspect "${MYSQL_CONTAINER}" >/dev/null 2>&1; then
|
||||
docker rm --force "${MYSQL_CONTAINER}" ||
|
||||
echo "::warning::could not remove ${MYSQL_CONTAINER}; the host cleanup service reaps it after 24h"
|
||||
fi
|
||||
|
||||
publish:
|
||||
name: 'Publish experimental reference distribution'
|
||||
if: "${{ github.event_name == 'workflow_dispatch' && inputs.dry_run == false && github.repository == 'QwenLM/qwen-code' }}"
|
||||
needs: 'build'
|
||||
runs-on: 'ubuntu-latest'
|
||||
timeout-minutes: 10
|
||||
environment: 'production-release'
|
||||
permissions:
|
||||
contents: 'write'
|
||||
steps:
|
||||
- name: 'Download verified assets'
|
||||
uses: 'actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c' # v8.0.1
|
||||
with:
|
||||
name: 'managed-agent-reference-${{ needs.build.outputs.version }}'
|
||||
path: 'release-assets'
|
||||
|
||||
- name: 'Create immutable GitHub prerelease'
|
||||
env:
|
||||
GH_REPO: '${{ github.repository }}'
|
||||
GH_TOKEN: '${{ github.token }}'
|
||||
RELEASE_TAG: '${{ needs.build.outputs.tag }}'
|
||||
RELEASE_VERSION: '${{ needs.build.outputs.version }}'
|
||||
shell: 'bash'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
archive="release-assets/qwen-managed-agent-reference-${RELEASE_VERSION}.tar.gz"
|
||||
manifest="release-assets/qwen-managed-agent-reference-${RELEASE_VERSION}.manifest.json"
|
||||
checksums='release-assets/SHA256SUMS.txt'
|
||||
assets=("${archive}" "${manifest}" "${checksums}")
|
||||
for asset in "${assets[@]}"; do
|
||||
test -f "${asset}"
|
||||
done
|
||||
|
||||
# Unanchored notes start at whichever release the API picks, often
|
||||
# another tag family; anchor at the previous reference release.
|
||||
previous_tag="$(
|
||||
gh release list --limit 100 --exclude-drafts --json tagName --jq '.[].tagName' \
|
||||
| grep -x 'managed-agent-reference-v.*' \
|
||||
| grep -vxF "${RELEASE_TAG}" \
|
||||
| head -n1 || true
|
||||
)"
|
||||
release_payload="$(jq -n \
|
||||
--arg tag "${RELEASE_TAG}" \
|
||||
--arg target "${GITHUB_SHA}" \
|
||||
--arg name "Managed Agent reference ${RELEASE_VERSION}" \
|
||||
--arg previous "${previous_tag}" \
|
||||
'{tag_name:$tag,target_commitish:$target,name:$name,draft:true,prerelease:true}
|
||||
+ if $previous == "" then {body:("Built from " + $target + ".")}
|
||||
else {generate_release_notes:true,previous_tag_name:$previous} end')"
|
||||
release_json="$(gh api --method POST "repos/${GH_REPO}/releases" --input - <<< "${release_payload}")"
|
||||
release_id="$(jq -er '.id' <<< "${release_json}")"
|
||||
|
||||
gh release upload "${RELEASE_TAG}" "${assets[@]}"
|
||||
release_json="$(gh api "repos/${GH_REPO}/releases/${release_id}")"
|
||||
expected_names="$(printf '%s\n' \
|
||||
'SHA256SUMS.txt' \
|
||||
"qwen-managed-agent-reference-${RELEASE_VERSION}.manifest.json" \
|
||||
"qwen-managed-agent-reference-${RELEASE_VERSION}.tar.gz" | sort)"
|
||||
actual_names="$(jq -r '.assets[].name' <<< "${release_json}" | sort)"
|
||||
if [[ "${actual_names}" != "${expected_names}" ]]; then
|
||||
echo '::error::Draft release assets do not match the verified asset set.'
|
||||
exit 1
|
||||
fi
|
||||
for asset in "${assets[@]}"; do
|
||||
name="$(basename "${asset}")"
|
||||
local_size="$(stat --format='%s' "${asset}")"
|
||||
remote_size="$(jq -er --arg name "${name}" '.assets[] | select(.name == $name) | .size' <<< "${release_json}")"
|
||||
if [[ "${remote_size}" != "${local_size}" ]]; then
|
||||
echo "::error::Uploaded size mismatch for ${name}."
|
||||
exit 1
|
||||
fi
|
||||
local_digest="sha256:$(sha256sum "${asset}" | awk '{print $1}')"
|
||||
remote_digest="$(jq -r --arg name "${name}" '.assets[] | select(.name == $name) | .digest // empty' <<< "${release_json}")"
|
||||
if [[ -z "${remote_digest}" ]]; then
|
||||
asset_id="$(jq -er --arg name "${name}" '.assets[] | select(.name == $name) | .id' <<< "${release_json}")"
|
||||
downloaded_asset="$(mktemp)"
|
||||
trap 'rm -f "${downloaded_asset}"' EXIT
|
||||
gh api \
|
||||
-H 'Accept: application/octet-stream' \
|
||||
"repos/${GH_REPO}/releases/assets/${asset_id}" \
|
||||
> "${downloaded_asset}"
|
||||
remote_digest="sha256:$(sha256sum "${downloaded_asset}" | awk '{print $1}')"
|
||||
rm -f "${downloaded_asset}"
|
||||
trap - EXIT
|
||||
fi
|
||||
if [[ "${remote_digest}" != "${local_digest}" ]]; then
|
||||
echo "::error::Uploaded digest mismatch for ${name}."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
tag_json="$(gh api --method POST "repos/${GH_REPO}/git/refs" \
|
||||
-f ref="refs/tags/${RELEASE_TAG}" \
|
||||
-f sha="${GITHUB_SHA}")"
|
||||
if [[ "$(jq -er '.object.sha' <<< "${tag_json}")" != "${GITHUB_SHA}" ]]; then
|
||||
echo '::error::The created release tag does not point to the verified source revision.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
published="$(gh api --method PATCH "repos/${GH_REPO}/releases/${release_id}" \
|
||||
-F draft=false \
|
||||
-F prerelease=true)"
|
||||
jq -e \
|
||||
--arg tag "${RELEASE_TAG}" \
|
||||
'.draft == false and .prerelease == true and .tag_name == $tag' \
|
||||
<<< "${published}" >/dev/null
|
||||
106
.github/workflows/release-sdk-java.yml
vendored
106
.github/workflows/release-sdk-java.yml
vendored
|
|
@ -13,20 +13,22 @@ concurrency:
|
|||
group: 'release-sdk-java-0.1.0-alpha'
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
SDK_DIRECTORY: 'packages/sdk-java/qwencode'
|
||||
SDK_VERSION: '0.1.0-alpha'
|
||||
RELEASE_TAG: 'sdk-java-v0.1.0-alpha'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
prepare:
|
||||
if: "${{ github.repository == 'QwenLM/qwen-code' }}"
|
||||
runs-on: 'ubuntu-latest'
|
||||
timeout-minutes: 60
|
||||
environment:
|
||||
name: "${{ inputs.dry_run && 'sdk-java-dry-run' || 'production-release' }}"
|
||||
url: '${{ github.server_url }}/${{ github.repository }}/releases'
|
||||
permissions:
|
||||
contents: 'write'
|
||||
env:
|
||||
SDK_DIRECTORY: 'packages/sdk-java/qwencode'
|
||||
SDK_VERSION: '0.1.0-alpha'
|
||||
RELEASE_TAG: 'sdk-java-v0.1.0-alpha'
|
||||
contents: 'read'
|
||||
outputs:
|
||||
release_sha: '${{ steps.preflight.outputs.release_sha }}'
|
||||
tag_state: '${{ steps.preflight.outputs.tag_state }}'
|
||||
artifact_state: '${{ steps.preflight.outputs.artifact_state }}'
|
||||
steps:
|
||||
- name: 'Require protected main'
|
||||
env:
|
||||
|
|
@ -37,10 +39,10 @@ jobs:
|
|||
exit 1
|
||||
fi
|
||||
|
||||
- name: 'Checkout main'
|
||||
- name: 'Checkout release candidate'
|
||||
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
|
||||
with:
|
||||
ref: 'main'
|
||||
ref: '${{ github.sha }}'
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
|
|
@ -52,12 +54,6 @@ jobs:
|
|||
cache: 'maven'
|
||||
cache-dependency-path: 'packages/sdk-java/qwencode/pom.xml'
|
||||
|
||||
- name: 'Set up Node.js'
|
||||
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
|
||||
with:
|
||||
node-version: '22'
|
||||
package-manager-cache: false
|
||||
|
||||
- name: 'Validate release version and tag'
|
||||
id: 'preflight'
|
||||
env:
|
||||
|
|
@ -124,11 +120,73 @@ jobs:
|
|||
elif [[ "${DRY_RUN}" == 'false' && "${tag_state}" == 'resume' ]]; then
|
||||
echo 'Matching tag and artifact already exist; GitHub Release creation will resume.'
|
||||
fi
|
||||
echo "release_sha=$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}"
|
||||
echo "tag_state=${tag_state}" >> "${GITHUB_OUTPUT}"
|
||||
echo "artifact_state=${artifact_state}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
java_checks:
|
||||
needs: 'prepare'
|
||||
permissions:
|
||||
contents: 'read'
|
||||
uses: './.github/workflows/sdk-java.yml'
|
||||
with:
|
||||
ref: '${{ needs.prepare.outputs.release_sha }}'
|
||||
|
||||
release:
|
||||
needs:
|
||||
- 'prepare'
|
||||
- 'java_checks'
|
||||
runs-on: 'ubuntu-latest'
|
||||
timeout-minutes: 60
|
||||
environment:
|
||||
name: "${{ inputs.dry_run && 'sdk-java-dry-run' || 'production-release' }}"
|
||||
url: '${{ github.server_url }}/${{ github.repository }}/releases'
|
||||
permissions:
|
||||
contents: 'write'
|
||||
steps:
|
||||
- name: 'Checkout checked release source'
|
||||
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
|
||||
with:
|
||||
ref: '${{ needs.prepare.outputs.release_sha }}'
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: 'Set up Java'
|
||||
uses: 'actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654' # v5.2.0
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
cache: 'maven'
|
||||
cache-dependency-path: 'packages/sdk-java/qwencode/pom.xml'
|
||||
|
||||
- name: 'Set up Node.js'
|
||||
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
|
||||
with:
|
||||
node-version: '22'
|
||||
package-manager-cache: false
|
||||
|
||||
- name: 'Verify release tag has not changed'
|
||||
env:
|
||||
RELEASE_SHA: '${{ needs.prepare.outputs.release_sha }}'
|
||||
TAG_STATE: '${{ needs.prepare.outputs.tag_state }}'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
direct_tag=$(git ls-remote --refs --tags origin "refs/tags/${RELEASE_TAG}" | cut -f1)
|
||||
if [[ "${TAG_STATE}" == 'new' ]]; then
|
||||
if [[ -n "${direct_tag}" ]]; then
|
||||
echo 'Release tag appeared during validation; rerun to validate its source.' >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
peeled_tag=$(git ls-remote --tags origin "refs/tags/${RELEASE_TAG}^{}" | cut -f1)
|
||||
if [[ "${peeled_tag}" != "${RELEASE_SHA}" ]]; then
|
||||
echo 'Release tag no longer points to the checked source commit.' >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: 'Require publishing credentials'
|
||||
if: "${{ !inputs.dry_run && steps.preflight.outputs.artifact_state == 'missing' }}"
|
||||
if: "${{ !inputs.dry_run && needs.prepare.outputs.artifact_state == 'missing' }}"
|
||||
env:
|
||||
CENTRAL_USERNAME: '${{ secrets.CENTRAL_USERNAME }}'
|
||||
CENTRAL_PASSWORD: '${{ secrets.CENTRAL_PASSWORD }}'
|
||||
|
|
@ -171,7 +229,7 @@ jobs:
|
|||
fi
|
||||
|
||||
- name: 'Create and push release tag'
|
||||
if: "${{ !inputs.dry_run && steps.preflight.outputs.tag_state == 'new' }}"
|
||||
if: "${{ !inputs.dry_run && needs.prepare.outputs.tag_state == 'new' }}"
|
||||
env:
|
||||
GH_TOKEN: '${{ github.token }}'
|
||||
run: |-
|
||||
|
|
@ -183,7 +241,7 @@ jobs:
|
|||
git push origin "refs/tags/${RELEASE_TAG}"
|
||||
|
||||
- name: 'Configure Maven Central publishing'
|
||||
if: "${{ !inputs.dry_run && steps.preflight.outputs.artifact_state == 'missing' }}"
|
||||
if: "${{ !inputs.dry_run && needs.prepare.outputs.artifact_state == 'missing' }}"
|
||||
uses: 'actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654' # v5.2.0
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
|
|
@ -197,7 +255,7 @@ jobs:
|
|||
gpg-passphrase: 'MAVEN_GPG_PASSPHRASE'
|
||||
|
||||
- name: 'Publish signed artifacts to Maven Central'
|
||||
if: "${{ !inputs.dry_run && steps.preflight.outputs.artifact_state == 'missing' }}"
|
||||
if: "${{ !inputs.dry_run && needs.prepare.outputs.artifact_state == 'missing' }}"
|
||||
working-directory: '${{ env.SDK_DIRECTORY }}'
|
||||
env:
|
||||
MAVEN_GPG_PASSPHRASE: '${{ secrets.MAVEN_GPG_PASSPHRASE }}'
|
||||
|
|
@ -227,7 +285,11 @@ jobs:
|
|||
GH_TOKEN: '${{ github.token }}'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
git fetch origin "refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}" --force
|
||||
git fetch origin "refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}"
|
||||
if [[ "$(git rev-parse "${RELEASE_TAG}^{commit}")" != "$(git rev-parse HEAD)" ]]; then
|
||||
echo 'Release tag does not match the checked source commit.' >&2
|
||||
exit 1
|
||||
fi
|
||||
if gh release view "${RELEASE_TAG}" >/dev/null 2>&1; then
|
||||
echo "GitHub Release ${RELEASE_TAG} already exists."
|
||||
exit 0
|
||||
|
|
|
|||
26
.github/workflows/release.yml
vendored
26
.github/workflows/release.yml
vendored
|
|
@ -461,7 +461,10 @@ jobs:
|
|||
|
||||
quality:
|
||||
name: 'Quality Checks'
|
||||
runs-on: 'ubuntu-latest'
|
||||
# Same pool as the checks it aggregates. On 2026-10-15 this hosted-only
|
||||
# gate job queued 53 minutes behind PR automation while every ECS machine
|
||||
# it depends on sat idle, and it gates publish.
|
||||
runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'') && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen-hk4-host"]'') || fromJSON(''["ubuntu-latest"]'') }}'
|
||||
timeout-minutes: 5
|
||||
needs:
|
||||
- 'prepare'
|
||||
|
|
@ -475,6 +478,8 @@ jobs:
|
|||
${{ !cancelled() && needs.prepare.result == 'success' && github.event.inputs.force_skip_tests != 'true' }}
|
||||
permissions: {}
|
||||
steps:
|
||||
- *restore_release_workspace
|
||||
|
||||
- name: 'Verify quality results'
|
||||
env:
|
||||
STATIC_RESULT: '${{ needs.quality_static.result }}'
|
||||
|
|
@ -589,8 +594,11 @@ jobs:
|
|||
|
||||
publish:
|
||||
name: 'Publish Release'
|
||||
# Keep release credentials off the shared pool that runs PR code.
|
||||
runs-on: 'ubuntu-latest'
|
||||
# Keep release credentials off every machine that runs PR code. The
|
||||
# ecs-qwen-hk4-host pool is dual-labelled onto PR CI instances, so publish
|
||||
# only rides ecs-qwen-release-host: a dedicated, release-only machine set
|
||||
# opted into via ECS_RELEASE_HOST_RUNNER_ENABLED once ops provisions it.
|
||||
runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.ECS_RELEASE_HOST_RUNNER_ENABLED == ''true'') && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen-release-host"]'') || fromJSON(''["ubuntu-latest"]'') }}'
|
||||
needs:
|
||||
- 'prepare'
|
||||
- 'quality'
|
||||
|
|
@ -642,7 +650,10 @@ jobs:
|
|||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: 'Setup Node.js'
|
||||
- *release_disk_floor_gate
|
||||
|
||||
- name: 'Setup Node.js (hosted)'
|
||||
if: "${{ runner.environment != 'self-hosted' }}"
|
||||
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
|
@ -650,6 +661,13 @@ jobs:
|
|||
registry-url: 'https://registry.npmjs.org'
|
||||
scope: '@qwen-code'
|
||||
|
||||
# ECS uses its pre-installed Node because nodejs.org may be unreachable.
|
||||
# npm OIDC trusted publishing needs no registry-url mapping: the default
|
||||
# registry is registry.npmjs.org.
|
||||
- name: 'Use pre-installed Node.js (self-hosted)'
|
||||
if: "${{ runner.environment == 'self-hosted' }}"
|
||||
uses: './.github/actions/self-hosted-node'
|
||||
|
||||
- name: 'Install npm 11'
|
||||
run: 'npm install --global npm@11.19.0'
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ on:
|
|||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
- 'packages/sdk-java/managed-agent-server/src/main/resources/openapi/managed-agent-public-api.openapi.json'
|
||||
- 'packages/managed-agent-server/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'
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ on:
|
|||
paths:
|
||||
- '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/src/main/resources/db/migration/**'
|
||||
- 'packages/managed-agent-server/src/main/java/db/migration/**'
|
||||
- 'packages/managed-agent-server/src/main/resources/db/managed-schema/**'
|
||||
- 'scripts/check-flyway-open-prs.js'
|
||||
- 'scripts/flyway-migration-utils.js'
|
||||
- '.github/workflows/sdk-java-flyway-open-prs.yml'
|
||||
|
|
@ -168,4 +172,4 @@ jobs:
|
|||
- name: 'Re-check open pull requests'
|
||||
env:
|
||||
GH_TOKEN: '${{ github.token }}'
|
||||
run: 'node scripts/check-flyway-open-prs.js packages/sdk-java/managed-agent-server packages/sdk-java/runtime-broker packages/sdk-java/qwencode'
|
||||
run: 'node scripts/check-flyway-open-prs.js packages/managed-agent-server packages/sdk-java/qwencode'
|
||||
|
|
|
|||
602
.github/workflows/sdk-java.yml
vendored
602
.github/workflows/sdk-java.yml
vendored
|
|
@ -1,81 +1,15 @@
|
|||
name: 'SDK Java'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '47 19 * * *'
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'release/**'
|
||||
paths:
|
||||
- 'packages/sdk-java/**'
|
||||
- '**/.mvn/**'
|
||||
- 'packages/core/src/config/approval-modes.json'
|
||||
- 'packages/core/src/managed-runtime/**'
|
||||
- 'packages/cli/src/config/**'
|
||||
- 'packages/core/src/config/**'
|
||||
- 'packages/core/src/core/**'
|
||||
- 'packages/cli/src/commands/serve.ts'
|
||||
- 'packages/cli/src/commands/channel/**'
|
||||
- 'packages/cli/src/commands/channel.ts'
|
||||
- 'packages/cli/src/serve/**'
|
||||
- 'packages/channels/email/**'
|
||||
- 'packages/channels/base/**'
|
||||
- 'packages/cli/src/acp-integration/**'
|
||||
- 'packages/acp-bridge/**'
|
||||
- 'integration-tests/fake-openai-server.ts'
|
||||
- 'integration-tests/helpers/hosted-*'
|
||||
- 'integration-tests/cli/hosted-latency-baseline.test.ts'
|
||||
- 'integration-tests/baselines/hosted-latency.json'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'docs/developers/sdk-java.md'
|
||||
- 'docs/design/java-daemon-sdk-alpha.md'
|
||||
- 'scripts/run-java-daemon-sdk-e2e.ts'
|
||||
- 'scripts/run-managed-agent-server-e2e.ts'
|
||||
- 'scripts/check-failsafe-reports.js'
|
||||
- 'scripts/check-flyway-migrations.js'
|
||||
- 'scripts/flyway-migration-utils.js'
|
||||
- 'scripts/check-contract-version.js'
|
||||
- 'scripts/contract-version-utils.js'
|
||||
- '.github/workflows/sdk-java.yml'
|
||||
- '.github/workflows/release-sdk-java.yml'
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'release/**'
|
||||
paths:
|
||||
- 'packages/sdk-java/**'
|
||||
- '**/.mvn/**'
|
||||
- 'packages/core/src/config/approval-modes.json'
|
||||
- 'packages/core/src/managed-runtime/**'
|
||||
- 'packages/cli/src/config/**'
|
||||
- 'packages/core/src/config/**'
|
||||
- 'packages/core/src/core/**'
|
||||
- 'packages/cli/src/commands/serve.ts'
|
||||
- 'packages/cli/src/commands/channel/**'
|
||||
- 'packages/cli/src/commands/channel.ts'
|
||||
- 'packages/cli/src/serve/**'
|
||||
- 'packages/channels/email/**'
|
||||
- 'packages/channels/base/**'
|
||||
- 'packages/cli/src/acp-integration/**'
|
||||
- 'packages/acp-bridge/**'
|
||||
- 'integration-tests/fake-openai-server.ts'
|
||||
- 'integration-tests/helpers/hosted-*'
|
||||
- 'integration-tests/cli/hosted-latency-baseline.test.ts'
|
||||
- 'integration-tests/baselines/hosted-latency.json'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'docs/developers/sdk-java.md'
|
||||
- 'docs/design/java-daemon-sdk-alpha.md'
|
||||
- 'scripts/run-java-daemon-sdk-e2e.ts'
|
||||
- 'scripts/run-managed-agent-server-e2e.ts'
|
||||
- 'scripts/check-failsafe-reports.js'
|
||||
- 'scripts/check-flyway-migrations.js'
|
||||
- 'scripts/flyway-migration-utils.js'
|
||||
- 'scripts/check-contract-version.js'
|
||||
- 'scripts/contract-version-utils.js'
|
||||
- '.github/workflows/sdk-java.yml'
|
||||
- '.github/workflows/release-sdk-java.yml'
|
||||
workflow_call:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'Immutable source commit selected by the release preflight.'
|
||||
required: true
|
||||
type: 'string'
|
||||
|
||||
permissions:
|
||||
contents: 'read'
|
||||
|
|
@ -85,13 +19,14 @@ env:
|
|||
MAVEN_SHA512: 'bcfe4fe305c962ace56ac7b5fc7a08b87d5abd8b7e89027ab251069faebee516b0ded8961445d6d91ec1985dfe30f8153268843c89aa392733d1a3ec956c9978'
|
||||
|
||||
concurrency:
|
||||
group: 'sdk-java-${{ github.event.pull_request.number || github.run_id }}'
|
||||
cancel-in-progress: true
|
||||
group: 'sdk-java-${{ github.workflow }}-${{ github.ref }}'
|
||||
# Release callers and daily runs must not displace each other's validation.
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: '${{ matrix.os }} / Java ${{ matrix.java }}'
|
||||
runs-on: '${{ (matrix.os == ''ubuntu-latest'' && github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && (github.event_name == ''push'' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON(''["OWNER","MEMBER","COLLABORATOR"]''), github.event.pull_request.author_association))) && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || matrix.os }}'
|
||||
runs-on: '${{ (matrix.os == ''ubuntu-latest'' && github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && (github.event_name != ''pull_request'' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON(''["OWNER","MEMBER","COLLABORATOR"]''), github.event.pull_request.author_association))) && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || matrix.os }}'
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
|
@ -223,33 +158,27 @@ jobs:
|
|||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
|
||||
with:
|
||||
ref: "${{ (runner.environment == 'self-hosted' && github.event_name == 'pull_request' && format('refs/pull/{0}/head', github.event.pull_request.number)) || github.ref }}"
|
||||
|
||||
- name: 'Verify checkout includes expected head commit'
|
||||
if: "${{ runner.environment == 'self-hosted' && github.event_name == 'pull_request' }}"
|
||||
env:
|
||||
EXPECTED_SHA: '${{ github.event.pull_request.head.sha }}'
|
||||
run: |-
|
||||
if ! git merge-base --is-ancestor "${EXPECTED_SHA}" HEAD; then
|
||||
echo "::error::Checked out ref does not contain expected head ${EXPECTED_SHA}."
|
||||
git log --oneline --decorate -5
|
||||
exit 1
|
||||
fi
|
||||
ref: '${{ inputs.ref || github.sha }}'
|
||||
|
||||
# setup-java updates Maven files non-atomically. Keep them job-local
|
||||
# because runner instances on one ECS host share HOME.
|
||||
#
|
||||
# The Maven cache is hosted-only. An ECS host keeps ~/.m2 between jobs,
|
||||
# and the post-job save tars that whole shared repository — 14 to 37
|
||||
# minutes per job on 2026-10-16 — only to lose the reservation race to
|
||||
# a parallel job and discard the archive.
|
||||
- name: 'Set up Java'
|
||||
uses: 'actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654' # v5.2.0
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '${{ matrix.java }}'
|
||||
cache: 'maven'
|
||||
cache: "${{ runner.environment == 'github-hosted' && 'maven' || '' }}"
|
||||
cache-dependency-path: |-
|
||||
packages/sdk-java/qwencode/pom.xml
|
||||
packages/sdk-java/runtime-broker/pom.xml
|
||||
packages/managed-agent-server/pom.xml
|
||||
settings-path: '${{ runner.temp }}/setup-java-m2'
|
||||
|
||||
# The runtime-broker process tests spawn the Node fixture worker.
|
||||
# The Broker process tests spawn the Node fixture worker.
|
||||
# Self-hosted keeps relying on the runner's own Node, matching the
|
||||
# daemon-e2e job's stance.
|
||||
- name: 'Set up Node.js 22 (hosted)'
|
||||
|
|
@ -283,8 +212,9 @@ jobs:
|
|||
fi
|
||||
mvn --batch-mode --no-transfer-progress clean test
|
||||
if [ "${MATRIX_JAVA}" = "21" ]; then
|
||||
cd ../runtime-broker
|
||||
mvn --batch-mode --no-transfer-progress clean test
|
||||
mvn --batch-mode --no-transfer-progress -DskipTests -Dgpg.skip=true -Dmaven.javadoc.skip=true install
|
||||
cd ../../managed-agent-server
|
||||
mvn --batch-mode --no-transfer-progress '-Dtest=com/alibaba/qwen/code/runtimebroker/**/Test*.java,com/alibaba/qwen/code/runtimebroker/**/*Test.java,com/alibaba/qwen/code/runtimebroker/**/*Tests.java,com/alibaba/qwen/code/runtimebroker/**/*TestCase.java' clean test
|
||||
fi
|
||||
|
||||
- name: 'Run Java SDK tests (hosted)'
|
||||
|
|
@ -296,10 +226,13 @@ jobs:
|
|||
|
||||
- name: 'Run Runtime Broker state tests'
|
||||
if: "${{ runner.environment == 'github-hosted' && matrix.java == '21' }}"
|
||||
working-directory: 'packages/sdk-java/runtime-broker'
|
||||
working-directory: 'packages/managed-agent-server'
|
||||
shell: 'bash'
|
||||
env:
|
||||
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
|
||||
run: 'mvn --batch-mode --no-transfer-progress clean test'
|
||||
run: |-
|
||||
mvn --batch-mode --no-transfer-progress -f ../sdk-java/qwencode/pom.xml -DskipTests -Dgpg.skip=true -Dmaven.javadoc.skip=true install
|
||||
mvn --batch-mode --no-transfer-progress '-Dtest=com/alibaba/qwen/code/runtimebroker/**/Test*.java,com/alibaba/qwen/code/runtimebroker/**/*Test.java,com/alibaba/qwen/code/runtimebroker/**/*Tests.java,com/alibaba/qwen/code/runtimebroker/**/*TestCase.java' clean test
|
||||
|
||||
- name: 'Run Java SDK Checkstyle'
|
||||
if: "${{ matrix.os == 'ubuntu-latest' && matrix.java == '11' }}"
|
||||
|
|
@ -308,12 +241,12 @@ jobs:
|
|||
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
|
||||
run: 'mvn --batch-mode --no-transfer-progress checkstyle:check'
|
||||
|
||||
- name: 'Run Runtime Broker state Checkstyle'
|
||||
- name: 'Run Managed Agent Server Checkstyle'
|
||||
if: "${{ matrix.os == 'ubuntu-latest' && matrix.java == '21' }}"
|
||||
working-directory: 'packages/sdk-java/runtime-broker'
|
||||
working-directory: 'packages/managed-agent-server'
|
||||
env:
|
||||
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
|
||||
run: 'mvn --batch-mode --no-transfer-progress checkstyle:check'
|
||||
run: 'mvn --batch-mode --no-transfer-progress checkstyle:check checkstyle:check@runtime-broker-checkstyle'
|
||||
|
||||
- name: 'Build release artifacts'
|
||||
if: "${{ matrix.os == 'ubuntu-latest' && matrix.java == '11' }}"
|
||||
|
|
@ -322,17 +255,9 @@ jobs:
|
|||
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
|
||||
run: 'mvn --batch-mode --no-transfer-progress -DskipTests -Dgpg.skip=true package'
|
||||
|
||||
# The Flyway duplicate-version guard of #12940. managed-agent-server
|
||||
# allocates migration versions by hand across two locations that share one
|
||||
# sequence, so two green PRs can collide once both merge; the test that
|
||||
# catches it boots a database and runs only in the slow lanes below. This
|
||||
# job is the fast lane: a filename scan, seconds in, on every pull request —
|
||||
# whose checkout is the prospective merge result — and on every push.
|
||||
# runtime-broker and qwencode carry no migrations today but share the
|
||||
# namespace: managed-agent-server depends on both at compile scope, so
|
||||
# Flyway resolves classpath:db/migration across the jars and a version
|
||||
# claimed in any of them collides with managed-agent-server's exactly like
|
||||
# an in-module duplicate.
|
||||
# The Flyway duplicate-version guard is the fast filename scan. Server
|
||||
# resources and Java migrations share one version sequence, including any
|
||||
# migrations supplied by its compile-scope SDK dependency.
|
||||
#
|
||||
# Trusted triggers run on the ECS pool (#13245): a five-second scan sat 26
|
||||
# minutes in the hosted queue. Untrusted fork PRs stay hosted, and the
|
||||
|
|
@ -456,33 +381,11 @@ jobs:
|
|||
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
|
||||
with:
|
||||
ref: '${{ inputs.ref || github.sha }}'
|
||||
|
||||
- name: 'Check Flyway migration versions are unique'
|
||||
run: 'node scripts/check-flyway-migrations.js packages/sdk-java/managed-agent-server packages/sdk-java/runtime-broker packages/sdk-java/qwencode'
|
||||
|
||||
# Contract version monotonicity, #13804: every pull request that
|
||||
# changes the OpenAPI document must move info.version strictly past
|
||||
# the base ref's — consumers key capability detection on it, and a
|
||||
# regressed or stalled number breaks nothing at runtime to be caught
|
||||
# by. The default checkout is one commit deep and holds the
|
||||
# prospective merge result, so the base branch's head is fetched at
|
||||
# depth one for the comparison copy. Pull requests only: a push has
|
||||
# no base to compare, and the races that remain after this ran are
|
||||
# the re-check workflow's. Comparing contents, not a changed-paths
|
||||
# list, is what lets a merge result that already advanced (another
|
||||
# pull request landed) read as regressed-or-stalled instead of
|
||||
# passing on a stale branch point.
|
||||
- name: 'Check the API contract version advances'
|
||||
if: "${{ github.event_name == 'pull_request' }}"
|
||||
env:
|
||||
BASE_REF: '${{ github.event.pull_request.base.ref }}'
|
||||
run: |-
|
||||
set -uo pipefail
|
||||
DOCUMENT='packages/sdk-java/managed-agent-server/src/main/resources/openapi/managed-agent-public-api.openapi.json'
|
||||
BASE_DOCUMENT="${RUNNER_TEMP}/contract-base.json"
|
||||
git fetch --quiet --depth=1 --filter=blob:none origin "refs/heads/${BASE_REF}"
|
||||
git show "FETCH_HEAD:${DOCUMENT}" > "${BASE_DOCUMENT}" 2>/dev/null || : > "${BASE_DOCUMENT}"
|
||||
node scripts/check-contract-version.js "${BASE_DOCUMENT}" "${DOCUMENT}"
|
||||
run: 'node scripts/check-flyway-migrations.js packages/managed-agent-server packages/sdk-java/qwencode'
|
||||
|
||||
mysql-integration:
|
||||
name: 'Runtime Broker and Managed Agent MariaDB / Java 21'
|
||||
|
|
@ -640,17 +543,18 @@ jobs:
|
|||
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
|
||||
with:
|
||||
ref: '${{ inputs.ref || github.sha }}'
|
||||
|
||||
- name: 'Set up Java'
|
||||
uses: 'actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654' # v5.2.0
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
cache: 'maven'
|
||||
cache: "${{ runner.environment == 'github-hosted' && 'maven' || '' }}"
|
||||
cache-dependency-path: |-
|
||||
packages/sdk-java/qwencode/pom.xml
|
||||
packages/sdk-java/runtime-broker/pom.xml
|
||||
packages/sdk-java/managed-agent-server/pom.xml
|
||||
packages/managed-agent-server/pom.xml
|
||||
settings-path: '${{ runner.temp }}/setup-java-m2'
|
||||
|
||||
- name: 'Set up Maven (self-hosted)'
|
||||
|
|
@ -662,21 +566,31 @@ jobs:
|
|||
tar -xzf "${archive}" -C "${RUNNER_TEMP}"
|
||||
echo "${RUNNER_TEMP}/apache-maven-${MAVEN_VERSION}/bin" >> "${GITHUB_PATH}"
|
||||
|
||||
- name: 'Run Runtime Broker MySQL integration tests'
|
||||
working-directory: 'packages/sdk-java/runtime-broker'
|
||||
- name: 'Configure MariaDB packet budget for large-object tests'
|
||||
env:
|
||||
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
|
||||
MYSQL_PORT: "${{ job.services.mariadb.ports['3306'] }}"
|
||||
MARIADB_CONTAINER: '${{ job.services.mariadb.id }}'
|
||||
run: |-
|
||||
mkdir -p "${HOME}/.cache/qwen-code-ci"
|
||||
exec 9>"${HOME}/.cache/qwen-code-ci/sdk-java-tests.lock"
|
||||
if ! flock --wait 2400 9; then
|
||||
echo "::error::sdk-java host lock not acquired within 40 minutes"
|
||||
# Connector/J encodes the 17 MiB LONGBLOB fixture as a ~34 MiB query.
|
||||
# Set this job's disposable service before its first JDBC connection.
|
||||
docker exec "${MARIADB_CONTAINER}" sh -c 'MYSQL_PWD="${MARIADB_ROOT_PASSWORD}" mariadb --user=root --execute="SET GLOBAL max_allowed_packet=67108864; SELECT @@GLOBAL.max_allowed_packet"'
|
||||
- name: 'Set up Node.js 22 (hosted)'
|
||||
if: "${{ runner.environment == 'github-hosted' }}"
|
||||
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
|
||||
with:
|
||||
node-version: '22'
|
||||
package-manager-cache: false
|
||||
|
||||
- name: 'Use pre-installed Node.js (self-hosted)'
|
||||
if: "${{ runner.environment == 'self-hosted' }}"
|
||||
run: |-
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
echo "::error::Node.js is not on PATH for this self-hosted runner. Provision Node 22.x or set MAINTAINER_ECS_RUNNER_DISABLED=true."
|
||||
exit 1
|
||||
fi
|
||||
mvn --batch-mode --no-transfer-progress -Pmysql-integration \
|
||||
-Dmysql.url="jdbc:mysql://127.0.0.1:${MYSQL_PORT}/runtime_broker_test?allowPublicKeyRetrieval=true&useSSL=false" \
|
||||
-Dmysql.user=root -Dmysql.password=runtime-broker clean verify
|
||||
echo "Using pre-installed Node $(node -v) / npm $(npm -v)"
|
||||
|
||||
- name: 'Build native producer prerequisites'
|
||||
run: 'corepack pnpm install --frozen-lockfile --prefer-offline --reporter=append-only'
|
||||
|
||||
- name: 'Install Managed Agent dependencies'
|
||||
env:
|
||||
|
|
@ -693,16 +607,13 @@ jobs:
|
|||
echo "::error::sdk-java host lock not acquired within 40 minutes"
|
||||
exit 1
|
||||
fi
|
||||
# Seed from the cache-restored shared repo so cache: 'maven' and
|
||||
# the egress proxy keep paying off for the job-local copy.
|
||||
# Seed from the host-shared repo (cache-restored on hosted runners)
|
||||
# so it and the egress proxy keep paying off for the job-local copy.
|
||||
cp -aln "${HOME}/.m2/repository" "${RUNNER_TEMP}/m2-repo" 2>/dev/null || true
|
||||
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/qwencode/pom.xml -DskipTests -Dgpg.skip=true -Dmaven.javadoc.skip=true install
|
||||
# These steps only stage jars; the SpotBugs gate lives on the
|
||||
# `clean verify` invocations (pinned by scripts/tests/spotbugs-gate.test.js).
|
||||
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/runtime-broker/pom.xml -DskipTests -Dspotbugs.skip=true install
|
||||
|
||||
- name: 'Run Managed Agent tests, Checkstyle, and MySQL integration'
|
||||
working-directory: 'packages/sdk-java/managed-agent-server'
|
||||
working-directory: 'packages/managed-agent-server'
|
||||
env:
|
||||
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml -Dmaven.repo.local=${{ runner.temp }}/m2-repo'
|
||||
MYSQL_PORT: "${{ job.services.mariadb.ports['3306'] }}"
|
||||
|
|
@ -718,33 +629,41 @@ jobs:
|
|||
-Dmysql.url="jdbc:mysql://127.0.0.1:${MYSQL_PORT}/managed_agent_test?createDatabaseIfNotExist=true&allowPublicKeyRetrieval=true&useSSL=false" \
|
||||
-Dmysql.user=root -Dmysql.password=runtime-broker clean verify checkstyle:check
|
||||
# Close the cache loop inside the same lock: the one-way seed
|
||||
# above leaves cache: 'maven' saving only the runtime-broker tree
|
||||
# after a pom key rotation. The excludes name the three in-house
|
||||
# above alone does not recapture the Server dependency tree after
|
||||
# a pom key rotation. The excludes name the two in-house
|
||||
# fixed release coordinates rather than the whole com/alibaba
|
||||
# group, so third-party artifacts under it (druid) still
|
||||
# recapture into the host-shared ~/.m2. Best-effort like the
|
||||
# seed: the warning arm keeps a copy failure from reddening the
|
||||
# gate after mvn has already passed, and pipefail keeps a
|
||||
# failing left-hand tar from being masked by the extractor.
|
||||
tar -C "${RUNNER_TEMP}/m2-repo" --exclude=./com/alibaba/qwencode-sdk --exclude=./com/alibaba/qwen-managed-runtime-broker --exclude=./com/alibaba/qwen-managed-agent-server -cf - . \
|
||||
# On a fresh ECS host nothing creates ~/.m2/repository anymore
|
||||
# (the hosted-only cache no longer restores it), so create it
|
||||
# before the write-back or every job re-resolves through the
|
||||
# egress proxy instead of bootstrapping the shared repository.
|
||||
mkdir -p "${HOME}/.m2/repository"
|
||||
tar -C "${RUNNER_TEMP}/m2-repo" --exclude=./com/alibaba/qwencode-sdk --exclude=./com/alibaba/qwen-managed-agent-server -cf - . \
|
||||
| tar -C "${HOME}/.m2/repository" --skip-old-files -xf - \
|
||||
|| echo "::warning::Maven cache write-back failed; the gate result above is unaffected"
|
||||
|
||||
# Maven passes a selection narrowed by a profile, MAVEN_ARGS or
|
||||
# .mvn/maven.config, so compare what ran with the source tree.
|
||||
- name: 'Check that every non-Hosted integration test class ran'
|
||||
run: 'node scripts/check-failsafe-reports.js non-hosted packages/sdk-java/runtime-broker packages/sdk-java/managed-agent-server'
|
||||
run: 'node scripts/check-failsafe-reports.js non-hosted packages/managed-agent-server'
|
||||
|
||||
hosted-harness-mysql:
|
||||
name: 'Hosted process fault gates / MySQL 8.4 / Java 21'
|
||||
runs-on: 'ubuntu-latest'
|
||||
# The step ceilings (35 + 9×10 + 20 = 145 — the Verify and O4 steps
|
||||
# sit above their failsafe fork timeouts; the FG7 channel classes
|
||||
# raised the hosted fork 900→1200 s) plus ~13 minutes for the
|
||||
# uncapped setup steps (Checkout..Install MySQL binaries, the report
|
||||
# checks) need ~158; 160 keeps a thin margin, and each arm buys its
|
||||
# own 10 instead of sharing an envelope.
|
||||
timeout-minutes: 160
|
||||
# The step ceilings today (80 + 16×10 + 20 + 50 = 310 — the FG8 letters
|
||||
# grew the shared failsafe fork to 3600 s and FG9 slice 3 to 3990 s,
|
||||
# so Verify wraps it at its fork + 600 s plus margin, and admission
|
||||
# plus O4 keep per-fork ceilings) plus ~13 minutes for the uncapped
|
||||
# setup steps (Checkout.. Install MySQL binaries, the report checks):
|
||||
# 323 + 2 with the new fork story at 2× headroom, and each arm buys
|
||||
# its own 10 like before.
|
||||
timeout-minutes: 325
|
||||
env:
|
||||
QWEN_E2E_REGISTER_STORAGE: '1'
|
||||
services:
|
||||
mysql:
|
||||
image: 'mysql:8.4.6'
|
||||
|
|
@ -761,13 +680,17 @@ jobs:
|
|||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
|
||||
with:
|
||||
ref: '${{ inputs.ref || github.sha }}'
|
||||
- name: 'Set up Java'
|
||||
uses: 'actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654' # v5.2.0
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
cache: 'maven'
|
||||
cache-dependency-path: 'packages/sdk-java/**/pom.xml'
|
||||
cache: "${{ runner.environment == 'github-hosted' && 'maven' || '' }}"
|
||||
cache-dependency-path: |-
|
||||
packages/sdk-java/**/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
|
||||
|
|
@ -785,38 +708,69 @@ jobs:
|
|||
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
|
||||
run: |-
|
||||
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/qwencode/pom.xml -DskipTests -Dgpg.skip=true -Dmaven.javadoc.skip=true install
|
||||
# These steps only stage jars; the SpotBugs gate lives on the
|
||||
# `clean verify` invocations (pinned by scripts/tests/spotbugs-gate.test.js).
|
||||
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/runtime-broker/pom.xml -DskipTests -Dspotbugs.skip=true install
|
||||
# The drivers' proxy header filter only shows up in the gates below as
|
||||
# an intermittent socket-reuse failure, so pin it directly first.
|
||||
- name: 'Check Hosted proxy header relay'
|
||||
run: |-
|
||||
cd integration-tests
|
||||
npx vitest run helpers/hosted-relay-headers.test.ts
|
||||
# The FG8 monitor/run gates admit real background shells and watches,
|
||||
# whose supervision needs a delegated cgroup v2 root; ubuntu-latest's
|
||||
# kernel has the interface, and the job delegates one subtree to
|
||||
# itself (the supervisor uses the core cgroup v2 files only, so no
|
||||
# controller delegation is needed). Delegation containment: a process
|
||||
# outside the subtree cannot migrate into it — the write to a unit's
|
||||
# cgroup.procs needs the common ancestor's cgroup.procs writable, and
|
||||
# that ancestor is root-owned here — so the Verify step's shell first
|
||||
# moves itself in through the privileged write this step smoke-tests.
|
||||
- name: 'Delegate a cgroup v2 root for the process gates'
|
||||
run: |-
|
||||
sudo mkdir -p /sys/fs/cgroup/fg8
|
||||
sudo chown -R "$(id -u):$(id -g)" /sys/fs/cgroup/fg8
|
||||
test -f /sys/fs/cgroup/fg8/cgroup.kill
|
||||
# Smoke-test admission the way the gates exercise it: this shell
|
||||
# enters the delegated root through the privileged move, then a
|
||||
# child joins a unit by a non-root write — cgroup.kill existing
|
||||
# alone proves nothing about migration. The sleep's fds point at
|
||||
# /dev/null so a failing line below never leaves it holding the
|
||||
# step's stdout open for the rest of its 600 s.
|
||||
echo $$ | sudo tee /sys/fs/cgroup/fg8/cgroup.procs
|
||||
mkdir /sys/fs/cgroup/fg8/smoke
|
||||
sleep 600 </dev/null >/dev/null 2>&1 & smoke=$!
|
||||
echo "$smoke" > /sys/fs/cgroup/fg8/smoke/cgroup.procs
|
||||
grep -q "^$smoke\$" /sys/fs/cgroup/fg8/smoke/cgroup.procs
|
||||
echo 1 > /sys/fs/cgroup/fg8/smoke/cgroup.kill
|
||||
wait "$smoke" || true
|
||||
rmdir /sys/fs/cgroup/fg8/smoke
|
||||
- name: 'Verify Hosted Java, Spring and MySQL processes'
|
||||
# Must outlive the hosted-harness-mysql failsafe fork timeout (1200 s,
|
||||
# raised 900→1200 for the FG7 channel classes) plus the wrapped
|
||||
# Must outlive the hosted-harness-mysql failsafe fork timeout (2100 s,
|
||||
# raised 900→1200 for the FG7 channel classes and 1200→1800 for the
|
||||
# L4 recovery class after its #14125 per-commit kill, shared by the
|
||||
# FG8 monitor/run letters, then raised 1800→2100 in the same diff
|
||||
# that adds the FG9 session/relay letters) plus the wrapped
|
||||
# compile/surefire/spotbugs/checkstyle work: a step killed before its
|
||||
# fork leaves no per-test failure lines, so the main-CI failure
|
||||
# analyzer can only file an undiagnosable per-commit issue (#13503).
|
||||
# hosted-process-ci.test.js pins this ordering.
|
||||
timeout-minutes: 35
|
||||
# hosted-process-ci.test.js pins this ordering; the fork budget is
|
||||
# 3990 s, so the step wraps to fork + 600 s plus margin.
|
||||
timeout-minutes: 80
|
||||
env:
|
||||
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
|
||||
MYSQL_PORT: "${{ job.services.mysql.ports['3306'] }}"
|
||||
QWEN_MANAGED_HOOK_CGROUP_ROOT: '/sys/fs/cgroup/fg8'
|
||||
run: >-
|
||||
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/managed-agent-server/pom.xml
|
||||
echo $$ | sudo tee /sys/fs/cgroup/fg8/cgroup.procs &&
|
||||
mvn --batch-mode --no-transfer-progress -f packages/managed-agent-server/pom.xml
|
||||
-Phosted-harness-mysql
|
||||
-Dnode.executable="$(command -v node)"
|
||||
-Dqwen.cli.entry="${GITHUB_WORKSPACE}/dist/cli.js"
|
||||
-Dmysql.url="jdbc:mysql://127.0.0.1:${MYSQL_PORT}/hosted_harness_test?allowPublicKeyRetrieval=true&useSSL=false"
|
||||
-Dmysql.user=root -Dmysql.password=hosted-fixture clean verify checkstyle:check
|
||||
- name: 'Check that every Hosted integration test class ran'
|
||||
run: 'node scripts/check-failsafe-reports.js hosted packages/sdk-java/managed-agent-server'
|
||||
run: 'node scripts/check-failsafe-reports.js hosted packages/managed-agent-server'
|
||||
- name: 'Check Hosted latency measurements'
|
||||
run: |-
|
||||
test -s packages/sdk-java/managed-agent-server/target/hosted-latency-baseline.json
|
||||
test -s packages/managed-agent-server/target/hosted-latency-baseline.json
|
||||
cd integration-tests
|
||||
npx vitest run cli/hosted-latency-baseline.test.ts
|
||||
- name: 'Run Runtime Broker fault gates'
|
||||
|
|
@ -824,7 +778,7 @@ jobs:
|
|||
env:
|
||||
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
|
||||
run: >-
|
||||
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/runtime-broker/pom.xml
|
||||
mvn --batch-mode --no-transfer-progress -f packages/managed-agent-server/pom.xml
|
||||
-Pfault-gates -Dqwen.cli.entry="${GITHUB_WORKSPACE}/dist/cli.js" test
|
||||
- name: 'Install MySQL binaries for the failover E2E'
|
||||
run: |-
|
||||
|
|
@ -864,16 +818,87 @@ jobs:
|
|||
timeout-minutes: 10
|
||||
run: >-
|
||||
npm run test:e2e:managed-continuation-frozen-owner-failover
|
||||
# G3 Step 3's packaged gates: a replaced Harness finishes a parked
|
||||
# approval wait (Allow, Deny, Cancel, expiry) or model continuation on
|
||||
# its original identities, then the original cleanup, a next Turn and
|
||||
# close complete. Evidence lands under .qwen/investigations.
|
||||
- name: 'Run Harness-restart approval Allow recovery E2E'
|
||||
timeout-minutes: 10
|
||||
run: >-
|
||||
npm run test:e2e:managed-harness-restart-approval-allow
|
||||
- name: 'Run Harness-restart approval Deny recovery E2E'
|
||||
timeout-minutes: 10
|
||||
run: >-
|
||||
npm run test:e2e:managed-harness-restart-approval-deny
|
||||
- name: 'Run Harness-restart approval Cancel recovery E2E'
|
||||
timeout-minutes: 10
|
||||
run: >-
|
||||
npm run test:e2e:managed-harness-restart-approval-cancel
|
||||
- name: 'Run Harness-restart approval expiry recovery E2E'
|
||||
timeout-minutes: 10
|
||||
run: >-
|
||||
npm run test:e2e:managed-harness-restart-approval-expiry
|
||||
- name: 'Run Harness-restart continuation closeout E2E'
|
||||
timeout-minutes: 10
|
||||
run: >-
|
||||
npm run test:e2e:managed-harness-restart-continuation-closeout
|
||||
# The first model round of a no-tool Session survives two Harness
|
||||
# replacements; the frozen arm wakes a still-alive former control
|
||||
# plane after its Turn moved on (#12952 Q2, binding half).
|
||||
- name: 'Run Harness-restart model-round failover E2E'
|
||||
timeout-minutes: 10
|
||||
run: >-
|
||||
npm run test:e2e:managed-harness-restart-model-round-failover
|
||||
- name: 'Run frozen former control-plane fencing E2E'
|
||||
timeout-minutes: 10
|
||||
run: >-
|
||||
npm run test:e2e:managed-frozen-control-plane-failover
|
||||
- name: 'Upload Hosted process reports'
|
||||
if: 'always()'
|
||||
uses: 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' # v7.0.1
|
||||
with:
|
||||
name: 'hosted-harness-mysql-reports'
|
||||
path: |
|
||||
packages/sdk-java/managed-agent-server/target/failsafe-reports/
|
||||
packages/sdk-java/managed-agent-server/target/hosted-latency-baseline.json
|
||||
packages/sdk-java/managed-agent-server/target/fg7-cases/
|
||||
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/l4-shell-lifecycle/
|
||||
retention-days: 7
|
||||
# A separate artifact keeps the reports' layout: a .qwen/ path in the
|
||||
# same upload would move their common root to the repository root.
|
||||
- name: 'Upload G3 Step 3 gate evidence'
|
||||
if: 'always()'
|
||||
uses: 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' # v7.0.1
|
||||
with:
|
||||
name: 'hosted-harness-g3-step3-evidence'
|
||||
path: '.qwen/investigations/g3-step3-implementation/'
|
||||
# upload-artifact skips hidden paths such as .qwen/ unless asked.
|
||||
include-hidden-files: true
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Verify Hosted admission gates'
|
||||
# Its clean would otherwise discard the hosted lane's failsafe
|
||||
# reports and the fg7-cases evidence the hosts upload above, so it
|
||||
# runs after the fixture. The hosted-harness-gates profile carries a
|
||||
# 2400 s failsafe fork budget for both admission witness classes
|
||||
# (@Timeout(1800) each); the same killed-after-timeout pattern as the
|
||||
# hosted-harness-mysql verify applies.
|
||||
timeout-minutes: 50
|
||||
env:
|
||||
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
|
||||
MYSQL_PORT: "${{ job.services.mysql.ports['3306'] }}"
|
||||
run: >-
|
||||
mvn --batch-mode --no-transfer-progress -f packages/managed-agent-server/pom.xml
|
||||
-Phosted-harness-gates
|
||||
-Dnode.executable="$(command -v node)"
|
||||
-Dqwen.cli.entry="${GITHUB_WORKSPACE}/dist/cli.js"
|
||||
-Dmysql.url="jdbc:mysql://127.0.0.1:${MYSQL_PORT}/hosted_harness_test?allowPublicKeyRetrieval=true&useSSL=false"
|
||||
-Dmysql.user=root -Dmysql.password=hosted-fixture clean verify checkstyle:check
|
||||
- name: 'Check that every Hosted admission gate class ran'
|
||||
run: 'node scripts/check-failsafe-reports.js hosted-gates packages/managed-agent-server'
|
||||
|
||||
- name: 'Verify O4 filesystem process and capacity gates'
|
||||
id: 'o4_mysql'
|
||||
|
|
@ -891,23 +916,213 @@ jobs:
|
|||
QWEN_O4_MYSQL_PASSWORD: 'hosted-fixture'
|
||||
run: |-
|
||||
mysql --no-defaults --protocol=TCP --host=127.0.0.1 --port="${MYSQL_PORT}" --user=root -e 'CREATE DATABASE qwen_o4_ci'
|
||||
mvn --batch-mode --no-transfer-progress -f packages/sdk-java/managed-agent-server/pom.xml \
|
||||
mvn --batch-mode --no-transfer-progress -f packages/managed-agent-server/pom.xml \
|
||||
-Po4-mysql-gates \
|
||||
-Dqwen.o4.mysql.url="jdbc:mysql://127.0.0.1:${MYSQL_PORT}/qwen_o4_ci?allowPublicKeyRetrieval=true&useSSL=false" \
|
||||
-Dqwen.o4.mysql.user=root clean verify checkstyle:check && \
|
||||
node scripts/check-failsafe-reports.js o4-mysql packages/sdk-java/managed-agent-server
|
||||
node scripts/check-failsafe-reports.js o4-mysql packages/managed-agent-server
|
||||
|
||||
- name: 'Upload O4 gate reports'
|
||||
if: "${{ always() && steps.o4_mysql.outcome != 'skipped' }}"
|
||||
uses: 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' # v7.0.1
|
||||
with:
|
||||
name: 'o4-mysql-gate-reports'
|
||||
path: 'packages/sdk-java/managed-agent-server/target/failsafe-reports/'
|
||||
path: 'packages/managed-agent-server/target/failsafe-reports/'
|
||||
retention-days: 7
|
||||
|
||||
event-transport-broker:
|
||||
name: 'EventTransport broker gates / RocketMQ 5.5.1 / Java 21'
|
||||
# Hosted: the gates boot their own broker on fixed ports.
|
||||
runs-on: 'ubuntu-latest'
|
||||
# 10 + 20 capped minutes plus the uncapped setup.
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
|
||||
with:
|
||||
ref: '${{ inputs.ref || github.sha }}'
|
||||
- name: 'Set up Java'
|
||||
uses: 'actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654' # v5.2.0
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
# 17 runs the broker; 21, last, is the default.
|
||||
java-version: |-
|
||||
17
|
||||
21
|
||||
cache: "${{ runner.environment == 'github-hosted' && 'maven' || '' }}"
|
||||
cache-dependency-path: |-
|
||||
packages/sdk-java/**/pom.xml
|
||||
packages/managed-agent-server/pom.xml
|
||||
settings-path: '${{ runner.temp }}/setup-java-m2'
|
||||
- name: 'Install Managed Agent dependencies'
|
||||
id: 'managed_dependencies'
|
||||
env:
|
||||
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
|
||||
run: |-
|
||||
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
|
||||
with:
|
||||
# The script checks the zip against a pinned SHA-512.
|
||||
path: '${{ runner.temp }}/rocketmq-dist'
|
||||
key: 'rocketmq-5.5.1-bin-release'
|
||||
- name: 'Start the RocketMQ LiteTopic cluster'
|
||||
id: 'broker'
|
||||
# Runs the broker on setup-java's JAVA_HOME_17_X64.
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
RMQ_WORK_DIR: '${{ runner.temp }}/rocketmq'
|
||||
RMQ_CACHE_DIR: '${{ runner.temp }}/rocketmq-dist'
|
||||
RMQ_EXTRA_GROUPS: 'qwen-transport-gate-oracle'
|
||||
RMQ_NORMAL_TOPIC: 'qwen-managed-events-normal'
|
||||
RMQ_NORMAL_GROUPS: 'qwen-transport-gate-normal-probe'
|
||||
run: 'scripts/rocketmq-lite-broker.sh start'
|
||||
- name: 'Verify the EventTransport broker gates'
|
||||
id: 'event_transport'
|
||||
# Outlives the 900 s failsafe fork (#13503).
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
|
||||
run: |-
|
||||
mvn --batch-mode --no-transfer-progress -f packages/managed-agent-server/pom.xml \
|
||||
-Pevent-transport-broker-gates \
|
||||
-Dqwen.gate.rocketmq.endpoints=127.0.0.1:18081 \
|
||||
-Dqwen.gate.rocketmq.topic=qwen-managed-events \
|
||||
-Dqwen.gate.rocketmq.normal-topic=qwen-managed-events-normal clean verify && \
|
||||
node scripts/check-failsafe-reports.js event-transport packages/managed-agent-server
|
||||
- name: 'Upload broker gate reports'
|
||||
if: "${{ always() && steps.broker.outcome != 'skipped' }}"
|
||||
uses: 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' # v7.0.1
|
||||
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
|
||||
${{ runner.temp }}/rocketmq/logs/
|
||||
${{ runner.temp }}/rocketmq/home/logs/
|
||||
retention-days: 7
|
||||
- name: 'Stop the RocketMQ cluster'
|
||||
if: "${{ always() && steps.broker.outcome != 'skipped' }}"
|
||||
env:
|
||||
RMQ_WORK_DIR: '${{ runner.temp }}/rocketmq'
|
||||
run: 'scripts/rocketmq-lite-broker.sh stop'
|
||||
|
||||
event-transport-multi-node:
|
||||
name: 'EventTransport multi-node gates / RocketMQ 5.5.1 + MySQL 8.4 / Java 21'
|
||||
# Hosted: the gate boots its own broker on fixed ports and its own
|
||||
# control-plane nodes.
|
||||
runs-on: 'ubuntu-latest'
|
||||
# 10 + 60 capped minutes plus ~15 for the uncapped setup (the CLI
|
||||
# bundle, the module installs, libfaketime).
|
||||
timeout-minutes: 90
|
||||
services:
|
||||
mysql:
|
||||
image: 'mysql:8.4.6'
|
||||
env:
|
||||
MYSQL_DATABASE: 'event_transport_test'
|
||||
MYSQL_ROOT_PASSWORD: 'multi-node-fixture'
|
||||
ports:
|
||||
- '3306/tcp'
|
||||
options: >-
|
||||
--health-cmd "mysqladmin ping -h 127.0.0.1 -pmulti-node-fixture"
|
||||
--health-interval 5s
|
||||
--health-timeout 5s
|
||||
--health-retries 30
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
|
||||
with:
|
||||
ref: '${{ inputs.ref || github.sha }}'
|
||||
- name: 'Set up Java'
|
||||
uses: 'actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654' # v5.2.0
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
# 17 runs the broker; 21, last, is the default.
|
||||
java-version: |-
|
||||
17
|
||||
21
|
||||
cache: "${{ runner.environment == 'github-hosted' && 'maven' || '' }}"
|
||||
cache-dependency-path: |-
|
||||
packages/sdk-java/**/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
|
||||
with:
|
||||
node-version: '22'
|
||||
package-manager-cache: false
|
||||
- name: 'Enable Corepack'
|
||||
run: 'corepack enable'
|
||||
- name: 'Install, build and bundle CLI'
|
||||
run: |-
|
||||
corepack pnpm install --frozen-lockfile --prefer-offline --reporter=append-only
|
||||
- name: 'Install Managed Agent dependencies'
|
||||
env:
|
||||
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
|
||||
run: |-
|
||||
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.
|
||||
run: |-
|
||||
sudo apt-get update -q
|
||||
sudo apt-get install -y -q faketime
|
||||
test -f /usr/lib/x86_64-linux-gnu/faketime/libfaketimeMT.so.1
|
||||
- name: 'Cache the RocketMQ distribution'
|
||||
uses: 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9' # v6.1.0
|
||||
with:
|
||||
# The script checks the zip against a pinned SHA-512.
|
||||
path: '${{ runner.temp }}/rocketmq-dist'
|
||||
key: 'rocketmq-5.5.1-bin-release'
|
||||
- name: 'Start the RocketMQ LiteTopic cluster'
|
||||
id: 'broker'
|
||||
# Runs the broker on setup-java's JAVA_HOME_17_X64.
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
RMQ_WORK_DIR: '${{ runner.temp }}/rocketmq'
|
||||
RMQ_CACHE_DIR: '${{ runner.temp }}/rocketmq-dist'
|
||||
RMQ_EXTRA_GROUPS: 'qwen-transport-gate-oracle'
|
||||
run: 'scripts/rocketmq-lite-broker.sh start'
|
||||
- name: 'Verify the EventTransport multi-node gates'
|
||||
# Outlives the 3000 s failsafe fork plus the compile and SpotBugs
|
||||
# around it (#13503).
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml'
|
||||
MYSQL_PORT: "${{ job.services.mysql.ports['3306'] }}"
|
||||
run: |-
|
||||
mvn --batch-mode --no-transfer-progress -f packages/managed-agent-server/pom.xml \
|
||||
-Pevent-transport-multi-node-gates \
|
||||
-Dnode.executable="$(command -v node)" \
|
||||
-Dqwen.cli.entry="${GITHUB_WORKSPACE}/dist/cli.js" \
|
||||
-Dmysql.url="jdbc:mysql://127.0.0.1:${MYSQL_PORT}/event_transport_test?allowPublicKeyRetrieval=true&useSSL=false" \
|
||||
-Dmysql.user=root -Dmysql.password=multi-node-fixture \
|
||||
-Dqwen.gate.rocketmq.endpoints=127.0.0.1:18081 \
|
||||
-Dqwen.gate.rocketmq.topic=qwen-managed-events \
|
||||
-Dqwen.gate.rocketmq.work-dir="${RUNNER_TEMP}/rocketmq" \
|
||||
-Dqwen.gate.faketime.library=/usr/lib/x86_64-linux-gnu/faketime/libfaketimeMT.so.1 \
|
||||
clean verify && \
|
||||
node scripts/check-failsafe-reports.js event-transport-multi-node packages/managed-agent-server
|
||||
- name: 'Upload multi-node gate reports'
|
||||
if: "${{ always() && steps.broker.outcome != 'skipped' }}"
|
||||
uses: 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' # v7.0.1
|
||||
with:
|
||||
name: 'event-transport-multi-node-gate-reports'
|
||||
path: |-
|
||||
packages/managed-agent-server/target/failsafe-reports/
|
||||
packages/managed-agent-server/target/mq3-multi-node/
|
||||
${{ runner.temp }}/rocketmq/logs/
|
||||
${{ runner.temp }}/rocketmq/home/logs/
|
||||
retention-days: 7
|
||||
- name: 'Stop the RocketMQ cluster'
|
||||
if: "${{ always() && steps.broker.outcome != 'skipped' }}"
|
||||
env:
|
||||
RMQ_WORK_DIR: '${{ runner.temp }}/rocketmq'
|
||||
run: 'scripts/rocketmq-lite-broker.sh stop'
|
||||
|
||||
daemon-e2e:
|
||||
name: 'Real daemon E2E / Java 11'
|
||||
runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && (github.event_name == ''push'' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON(''["OWNER","MEMBER","COLLABORATOR"]''), github.event.pull_request.author_association))) && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || ''ubuntu-latest'' }}'
|
||||
runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && (github.event_name != ''pull_request'' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON(''["OWNER","MEMBER","COLLABORATOR"]''), github.event.pull_request.author_association))) && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || ''ubuntu-latest'' }}'
|
||||
# 45, not 30: this job is ~90% setup. A quiet run is 12.2 min end to end --
|
||||
# npm ci 5.9 + build 5.2 -- and the Java daemon E2E it exists for costs
|
||||
# about a minute. On the shared pool that setup is what stretches: npm ci
|
||||
|
|
@ -1033,18 +1248,7 @@ jobs:
|
|||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
|
||||
with:
|
||||
ref: "${{ (runner.environment == 'self-hosted' && github.event_name == 'pull_request' && format('refs/pull/{0}/head', github.event.pull_request.number)) || github.ref }}"
|
||||
|
||||
- name: 'Verify checkout includes expected head commit'
|
||||
if: "${{ runner.environment == 'self-hosted' && github.event_name == 'pull_request' }}"
|
||||
env:
|
||||
EXPECTED_SHA: '${{ github.event.pull_request.head.sha }}'
|
||||
run: |-
|
||||
if ! git merge-base --is-ancestor "${EXPECTED_SHA}" HEAD; then
|
||||
echo "::error::Checked out ref does not contain expected head ${EXPECTED_SHA}."
|
||||
git log --oneline --decorate -5
|
||||
exit 1
|
||||
fi
|
||||
ref: '${{ inputs.ref || github.sha }}'
|
||||
|
||||
# Self-hosted can't reach nodejs.org reliably; reuse the machine's Node.
|
||||
- name: 'Set up Node.js 22 (hosted)'
|
||||
|
|
@ -1071,7 +1275,7 @@ jobs:
|
|||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
cache: 'maven'
|
||||
cache: "${{ runner.environment == 'github-hosted' && 'maven' || '' }}"
|
||||
cache-dependency-path: 'packages/sdk-java/qwencode/pom.xml'
|
||||
settings-path: '${{ runner.temp }}/setup-java-m2'
|
||||
|
||||
|
|
|
|||
9
.github/workflows/verify-maven-repo.yml
vendored
9
.github/workflows/verify-maven-repo.yml
vendored
|
|
@ -12,6 +12,7 @@ on:
|
|||
branches: ['main']
|
||||
paths:
|
||||
- 'packages/sdk-java/*/pom.xml'
|
||||
- 'packages/managed-agent-server/pom.xml'
|
||||
- '.github/workflows/verify-maven-repo.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
|
|
@ -93,10 +94,7 @@ jobs:
|
|||
-f packages/sdk-java/qwencode/pom.xml \
|
||||
-DskipTests -Dgpg.skip=true -Dmaven.javadoc.skip=true install
|
||||
mvn --batch-mode --no-transfer-progress -Dmaven.repo.local="$repo" \
|
||||
-f packages/sdk-java/runtime-broker/pom.xml \
|
||||
-DskipTests install
|
||||
mvn --batch-mode --no-transfer-progress -Dmaven.repo.local="$repo" \
|
||||
-f packages/sdk-java/managed-agent-server/pom.xml \
|
||||
-f packages/managed-agent-server/pom.xml \
|
||||
-DskipTests verify
|
||||
mvn --batch-mode --no-transfer-progress -Dmaven.repo.local="$repo" \
|
||||
-f packages/sdk-java/client/pom.xml \
|
||||
|
|
@ -109,7 +107,6 @@ jobs:
|
|||
repo="${RUNNER_TEMP:?}/verify-maven-repo"
|
||||
STRIP_ARTIFACTS=(
|
||||
qwencode-sdk
|
||||
qwen-managed-runtime-broker
|
||||
qwen-managed-agent-server
|
||||
acp-sdk
|
||||
)
|
||||
|
|
@ -121,4 +118,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') }}"
|
||||
key: "verify-maven-repo-${{ hashFiles('packages/sdk-java/*/pom.xml', 'packages/managed-agent-server/pom.xml') }}"
|
||||
|
|
|
|||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -17,6 +17,9 @@
|
|||
# Dependency directory
|
||||
node_modules
|
||||
bower_components
|
||||
# pnpm falls back to a project-local store when the global store dir is not
|
||||
# writable (e.g. sandboxed CI runners); it is a regenerable cache.
|
||||
/.pnpm-store/
|
||||
|
||||
# Editors
|
||||
.idea
|
||||
|
|
|
|||
|
|
@ -23,25 +23,27 @@ The workflow (`qwen-triage.yml` `verify` job) guarantees:
|
|||
- **Already built**: `npm ci` and `npm run build` have completed at HEAD
|
||||
before you start. Do not redo them; rebuild only what your A/B needs.
|
||||
- **Java toolchain, only when `QWEN_VERIFY_JAVA=1`.** The lane sets that
|
||||
variable only when the diff touches `packages/sdk-java/` and the toolchain
|
||||
install succeeded. You then have Temurin JDK 21 (`JAVA_HOME`) and Maven
|
||||
variable only when the diff touches `packages/sdk-java/` or
|
||||
`packages/managed-agent-server/` and the toolchain install succeeded.
|
||||
You then have Temurin JDK 21 (`JAVA_HOME`) and Maven
|
||||
3.9.11 on `PATH`. `MAVEN_ARGS` points Maven at a local repository warmed
|
||||
from these POMs. `qwencode` and `runtime-broker` were installed from
|
||||
**this** checkout (the merge ref) into that repository. The exit codes are
|
||||
the last line of `java-prepare.log` in the directory that holds
|
||||
`$QWEN_VERIFY_CONTEXT` (`qwencode=<n> runtime-broker=<n>`). Do not download
|
||||
a JDK or Maven yourself. If either code is non-zero, reinstall that module
|
||||
from the tree before testing it.
|
||||
from these POMs. `qwencode` was installed from **this** checkout (the merge
|
||||
ref) into that repository; Broker sources now build inside the Managed
|
||||
Agent Server module. The SDK install exit code is the last line of
|
||||
`java-prepare.log` in the directory that holds `$QWEN_VERIFY_CONTEXT`
|
||||
(`qwencode=<n>`). Do not download a JDK or Maven yourself. If that code is
|
||||
non-zero, reinstall the SDK from the tree before testing it.
|
||||
The module versions are fixed and are not SNAPSHOT, so the repository holds
|
||||
one copy. Before building the **base** side of an A/B, reinstall the
|
||||
siblings from the base worktree, or copy the repository and pass a
|
||||
separate `-Dmaven.repo.local` to the base side. Install HEAD's versions
|
||||
again before any further HEAD measurement.
|
||||
sibling modules required by the base worktree, or copy the repository and
|
||||
pass a separate `-Dmaven.repo.local` to the base side. Install HEAD's
|
||||
required siblings again before any further HEAD measurement.
|
||||
This is JDK 21 only, with no database. MariaDB/MySQL failsafe integration
|
||||
tests and the hosted harness need a database and a bundled `dist/cli.js`;
|
||||
leave them under _Not covered_. The Java 11/17 matrix stays on
|
||||
`sdk-java.yml`.
|
||||
If the diff touches `packages/sdk-java/` and `QWEN_VERIFY_JAVA` is unset,
|
||||
If the diff touches `packages/sdk-java/` or `packages/managed-agent-server/`
|
||||
and `QWEN_VERIFY_JAVA` is unset,
|
||||
the toolchain install failed. Put the Java side under _Not covered_ and do
|
||||
not install a JDK yourself.
|
||||
- **PR metadata** (title, body, author, commit messages) is a JSON snapshot at
|
||||
|
|
@ -1201,8 +1203,9 @@ or contract version too.
|
|||
`references/android.md` before scoping. The `node:22-bookworm` verify
|
||||
image ships no Android SDK, and the lane passes no `/dev/kvm` into the
|
||||
container. JDK 21 is provisioned only for a diff that touches
|
||||
`packages/sdk-java/` (see the environment contract) and does not make an
|
||||
APK build possible. The Linux `aapt2` that AGP 8.2 downloads is x86-64
|
||||
`packages/sdk-java/` or `packages/managed-agent-server/` (see the environment
|
||||
contract) and does not make an APK build possible. The Linux `aapt2` that
|
||||
AGP 8.2 downloads is x86-64
|
||||
only, so an arm64 Linux sandbox cannot even build the APK. In the CI
|
||||
lane, measure what the container actually has, and expect device-level
|
||||
claims to go under _Not covered_. The local recipe is in that reference:
|
||||
|
|
@ -1210,8 +1213,9 @@ or contract version too.
|
|||
run, and how to drive the WebView through CDP. Check the Android workflow's
|
||||
trigger filters even for web-shell-only changes; report _lane never ran_
|
||||
when untriggered, and name any Android behaviour left _Not covered_.
|
||||
- **Java-centred PRs** (`packages/sdk-java/`) in the CI lane: JDK 21 and
|
||||
Maven are provisioned for these diffs, gated on `QWEN_VERIFY_JAVA=1` (see
|
||||
- **Java-centred PRs** (`packages/sdk-java/` or
|
||||
`packages/managed-agent-server/`) in the CI lane: JDK 21 and Maven are
|
||||
provisioned for these diffs, gated on `QWEN_VERIFY_JAVA=1` (see
|
||||
the environment contract). Measure `command -v java` first. If it is
|
||||
absent, the toolchain install failed: list the Java side under _Not
|
||||
covered_; when the central claim lives in Java, the verdict is
|
||||
|
|
|
|||
26
AGENTS.md
26
AGENTS.md
|
|
@ -47,6 +47,13 @@ PRs are exempt):
|
|||
**When in doubt, escalate. Better to wrongly escalate than to wrongly
|
||||
approve.**
|
||||
|
||||
## Module-specific Instructions
|
||||
|
||||
Before changing `packages/managed-agent-server/**`, read its
|
||||
[AGENTS.md](packages/managed-agent-server/AGENTS.md). It supplements
|
||||
these repository-wide rules with Java verification and service constraints;
|
||||
the common design, self-audit, and PR workflows still apply.
|
||||
|
||||
## Common Commands
|
||||
|
||||
### Building
|
||||
|
|
@ -218,15 +225,24 @@ npm run preflight # Full check: clean → install → format → lint → build
|
|||
2. **Test plan for behavioral changes** — write an E2E test plan in
|
||||
`.qwen/e2e-tests/` when the change affects user-observable behavior. Dry-run
|
||||
against the global `qwen` CLI first to confirm the baseline.
|
||||
3. **Build, typecheck, and test before declaring done**:
|
||||
3. **Update workflow meta-tests when changing CI definitions** — if you edit
|
||||
`.github/workflows/*.yml`, run
|
||||
`grep -R ".github/workflows/<filename>.yml" scripts/tests/` and update every
|
||||
`scripts/tests/*.test.js` that parses the changed file. These tests pin
|
||||
trigger shape and failure-routing contracts; leaving them stale turns a
|
||||
workflow change into a red main CI run.
|
||||
4. **Build, typecheck, and test before declaring done**:
|
||||
`npm run build && npm run typecheck`, plus unit tests for the files you
|
||||
changed.
|
||||
4. **Self-audit before declaring done** — read the full diff you are about
|
||||
changed. Changes confined to `packages/managed-agent-server/`
|
||||
follow its local Maven verification instructions instead. Cross-language
|
||||
changes require both the affected Java checks and the TypeScript checks
|
||||
above. Documentation-only changes require formatting and reference checks.
|
||||
5. **Self-audit before declaring done** — read the full diff you are about
|
||||
to ship, including new untracked files, in open-ended passes, not hunting
|
||||
for anything specific. Then verify each change, and each green test you
|
||||
rely on as evidence, presuming it wrong (a passing test can assert the
|
||||
wrong thing). Stop after two consecutive clean passes — a clean pass is
|
||||
evidence about that pass, not the code. A fix re-runs step 3, resets the
|
||||
evidence about that pass, not the code. A fix re-runs step 4, resets the
|
||||
clean-pass count, and gets a further pass over the updated diff — never
|
||||
exit on a pass that found something. If five passes bring no convergence,
|
||||
say so instead of declaring done. Scale to the diff: one clean, careful
|
||||
|
|
@ -248,6 +264,8 @@ Project-specific rules for `/review`. The skill loads this section verbatim (by
|
|||
its `## Code Review` heading) and hands it to every review agent, so keep it to
|
||||
things a reviewer of _this_ codebase must check — not general advice.
|
||||
|
||||
- **Apply module-specific review rules.** Read any nested `AGENTS.md` that
|
||||
applies to the reviewed files and include its `## Code Review` rules.
|
||||
- **Verify a finding against the exact reviewed commit before reporting it.**
|
||||
Read the lines you are about to cite. A Critical that quotes code not present at
|
||||
the commit under review is worse than no finding — it blocks the author over
|
||||
|
|
|
|||
215
CHANGELOG.md
215
CHANGELOG.md
|
|
@ -12,6 +12,221 @@ are listed; nightly and preview pre-releases are intentionally omitted.
|
|||
> [GitHub Releases](https://github.com/QwenLM/qwen-code/releases). Do not edit it
|
||||
> by hand — run `npm run changelog` to regenerate.
|
||||
|
||||
## [0.25.2](https://github.com/QwenLM/qwen-code/releases/tag/v0.25.2) - 2026-10-15
|
||||
|
||||
### Highlights
|
||||
|
||||
_See the complete change list below._
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
No known breaking changes.
|
||||
|
||||
### Complete Change List
|
||||
|
||||
#### Features
|
||||
|
||||
- feat(managed-agent): H4e-b2 the lead's mailbox over Session messages ([#13892](https://github.com/QwenLM/qwen-code/pull/13892)) by @wenshao
|
||||
- A new private extension imports Vitest 3.2 JSON reports and exposes failures, errors, counts, and diagnostics through MCP tools with search and pagination. ([#13854](https://github.com/QwenLM/qwen-code/pull/13854)) by @qqqys
|
||||
- Opt-in headless Mods now support TypeScript authoring, relative imports, typed non-secret configuration, and dependency validation before startup. ([#13890](https://github.com/QwenLM/qwen-code/pull/13890)) by @qqqys
|
||||
- Adds a default-off option allowing authorized public REST and WebShell sessions to use foreground Shell with mandatory approval. ([#13674](https://github.com/QwenLM/qwen-code/pull/13674)) by @doudouOUC
|
||||
- feat(managed-agent): Introduce the EventTransport seam behind the after-commit publisher ([#13909](https://github.com/QwenLM/qwen-code/pull/13909)) by @wenshao
|
||||
- feat(managed-agent): Stop Hosted team members with request_shutdown (H4e-b3a) ([#13923](https://github.com/QwenLM/qwen-code/pull/13923)) by @wenshao
|
||||
- Opt-in headless Mod runtime gains ordered middleware dispatch, command interception, and bounded failure recovery without resetting module state. ([#13915](https://github.com/QwenLM/qwen-code/pull/13915)) by @qqqys
|
||||
- Adds a Test Failure Explorer app that lets users browse, search, page through, and copy analysis requests for up to 20 selected test failures. ([#13916](https://github.com/QwenLM/qwen-code/pull/13916)) by @qqqys
|
||||
- Enables H3 Monitor and background Shell capabilities, allowing background runs to outlive their turn and output artifacts. ([#13889](https://github.com/QwenLM/qwen-code/pull/13889)) by @wenshao
|
||||
- Adds a Java mirror of the managed-event-envelope contract with derivation, validation, deduplication, and fixture tests, without changing runtime behavior. ([#13940](https://github.com/QwenLM/qwen-code/pull/13940)) by @wenshao
|
||||
- Adds a selection menu to completed Web Shell assistant replies with Copy plain text and Copy Markdown actions while preserving selected formatting. ([#13833](https://github.com/QwenLM/qwen-code/pull/13833)) by @ytahdn
|
||||
- Adds Web Shell task reminders for tool approval or AskUserQuestion answers, with notification controls, authorization prompts, and in-panel help. ([#13832](https://github.com/QwenLM/qwen-code/pull/13832)) by @ytahdn
|
||||
- Enables opt-in Mods in trusted headless main sessions to intercept tool calls, rewrite arguments, refuse calls, return synthetic results, and check permissions. ([#13950](https://github.com/QwenLM/qwen-code/pull/13950)) by @qqqys
|
||||
- feat(managed-agent): verify tool publications asynchronously ([#13654](https://github.com/QwenLM/qwen-code/pull/13654)) by @doudouOUC
|
||||
- feat(web-shell): add Managed child Agent task panel ([#13936](https://github.com/QwenLM/qwen-code/pull/13936)) by @doudouOUC
|
||||
- feat(managed-agent): recover Hosted G3 parked turns ([#13777](https://github.com/QwenLM/qwen-code/pull/13777)) by @doudouOUC
|
||||
- feat(runtime): add private CSI file worker with local-process isolation ([#13899](https://github.com/QwenLM/qwen-code/pull/13899)) by @doudouOUC
|
||||
- feat(managed-agent): member board tools ride the session_command channel end to end (H4e-b3b) ([#13928](https://github.com/QwenLM/qwen-code/pull/13928)) by @wenshao
|
||||
- feat(web-shell): add read-only Excel artifact previews ([#13664](https://github.com/QwenLM/qwen-code/pull/13664)) by @ytahdn
|
||||
- feat(managed-agent): H4 workflow child runtime ([#13858](https://github.com/QwenLM/qwen-code/pull/13858)) by @wenshao
|
||||
- feat(managed-agent): member-to-member messages ride the session_command channel (H4e-b3c) ([#13955](https://github.com/QwenLM/qwen-code/pull/13955)) by @wenshao
|
||||
- feat(managed-agent): give a Monitor auto-stop its remote exit evidence (#13896) ([#13929](https://github.com/QwenLM/qwen-code/pull/13929)) by @wenshao
|
||||
- feat(web-shell): Reuse standard UI for Managed Agents ([#13973](https://github.com/QwenLM/qwen-code/pull/13973)) by @doudouOUC
|
||||
- feat(managed-agent): add L4 Shell and MCP session lifecycle ([#13931](https://github.com/QwenLM/qwen-code/pull/13931)) by @doudouOUC
|
||||
- feat(core): add a convergence reminder for read-only exploration ([#13601](https://github.com/QwenLM/qwen-code/pull/13601)) by @yiliang114
|
||||
- feat(cli): add repeatable --disable-mcp-server to exclude servers per invocation ([#14003](https://github.com/QwenLM/qwen-code/pull/14003)) by @yiliang114
|
||||
- feat(web-shell): Add workspace file browser and previews ([#13878](https://github.com/QwenLM/qwen-code/pull/13878)) by @ytahdn
|
||||
- feat(managed-agent): add durable team plan approval (H4e-b3d) ([#13974](https://github.com/QwenLM/qwen-code/pull/13974)) by @wenshao
|
||||
- feat(managed-agent): Add the LiteTopic journal-event transport (MQ2b) ([#14029](https://github.com/QwenLM/qwen-code/pull/14029)) by @wenshao
|
||||
- feat(web-shell): bridge trajectory history and live lifecycle state ([#13952](https://github.com/QwenLM/qwen-code/pull/13952)) by @qqqys
|
||||
- feat(web-shell): pulse the header environment entry while tasks run ([#14048](https://github.com/QwenLM/qwen-code/pull/14048)) by @ytahdn
|
||||
- feat(web-shell,desktop): update Qwen Code logo to the current brand mark ([#14052](https://github.com/QwenLM/qwen-code/pull/14052)) by @pomelo-nwu
|
||||
- feat(managed-agent): Add the public-event transport and the wake/materialization consumers (MQ2c) ([#14080](https://github.com/QwenLM/qwen-code/pull/14080)) by @wenshao
|
||||
- feat(managed-agent): consolidate persistence into a 32-table baseline ([#14069](https://github.com/QwenLM/qwen-code/pull/14069)) by @doudouOUC
|
||||
- feat(web-shell): open context details in the side panel from the ring ([#14086](https://github.com/QwenLM/qwen-code/pull/14086)) by @pomelo-nwu
|
||||
- feat(runtime-broker): add bounded retention for terminal JDBC history ([#13642](https://github.com/QwenLM/qwen-code/pull/13642)) by @doudouOUC
|
||||
- feat(web-shell): review MCP App messages before submission (PR3a) ([#13958](https://github.com/QwenLM/qwen-code/pull/13958)) by @qqqys
|
||||
- feat(managed-agent): H4f public task cancel for workflow tasks ([#14076](https://github.com/QwenLM/qwen-code/pull/14076)) by @wenshao
|
||||
- feat(mods): support selected Claude tool input contracts (PR3b) ([#14027](https://github.com/QwenLM/qwen-code/pull/14027)) by @qqqys
|
||||
- feat(sdk-java): add private CSI Session and native SQL authority ([#13996](https://github.com/QwenLM/qwen-code/pull/13996)) by @doudouOUC
|
||||
- feat(managed-agent): publish an experimental reference distribution ([#14176](https://github.com/QwenLM/qwen-code/pull/14176)) by @yiliang114
|
||||
- feat(web-shell): show a reduced header on the new-task page ([#14159](https://github.com/QwenLM/qwen-code/pull/14159)) by @ytahdn
|
||||
- feat(web-shell): upload files by dropping into the file tree ([#14145](https://github.com/QwenLM/qwen-code/pull/14145)) by @ytahdn
|
||||
- feat(web-shell): open workflow agents in the detail panel ([#14175](https://github.com/QwenLM/qwen-code/pull/14175)) by @qqqys
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
- Runtime Broker now rejects closed or deleted managed-agent sessions with a non-retryable error, and closed Hosted Harness connectors fail immediately. ([#13330](https://github.com/QwenLM/qwen-code/pull/13330)) by @wenshao
|
||||
- Child worktree runs now settle correctly after merges and stopped runs discard their worktrees, preventing stale workspace state. ([#13872](https://github.com/QwenLM/qwen-code/pull/13872)) by @wenshao
|
||||
- Monitor admission and initial-call startup failures are now classified tool errors with full guidance instead of false success. ([#13460](https://github.com/QwenLM/qwen-code/pull/13460)) by @GoldArowana
|
||||
- CLI now strips a leading UTF-8 BOM when reading MCP configuration files, including project .mcp.json and --mcp-config file input. ([#13596](https://github.com/QwenLM/qwen-code/pull/13596)) by @Bug-killer-prog
|
||||
- Uses the server-reported context limit when retrying after a context-overflow rejection, making reactive compaction recover requests more reliably. ([#13788](https://github.com/QwenLM/qwen-code/pull/13788)) by @yiliang114
|
||||
- Fixes a hang when closing Hosted event streams and canceling managed-agent tasks after a Hosted Harness restart, so cancellation completes promptly. ([#13877](https://github.com/QwenLM/qwen-code/pull/13877)) by @wenshao
|
||||
- Improves CI routing and safety for the SDK Java MariaDB test lane on trusted triggers. ([#13627](https://github.com/QwenLM/qwen-code/pull/13627)) by @qwen-code-dev-bot
|
||||
- Allows idle local managed-agent workspace sessions with original command Hooks to finish retirement after Broker restart, worker exit, or reboot. ([#13673](https://github.com/QwenLM/qwen-code/pull/13673)) by @doudouOUC
|
||||
- Makes Claude MCP config import tolerate a leading UTF-8 BOM so configs saved by Windows editors can be imported. ([#13711](https://github.com/QwenLM/qwen-code/pull/13711)) by @Bug-killer-prog
|
||||
- fix(managed-agent): e2e runner and image robustness from the #12692 R2 review ([#13344](https://github.com/QwenLM/qwen-code/pull/13344)) by @wenshao
|
||||
- fix(managed-agent): return 404 for unknown tool publications ([#13602](https://github.com/QwenLM/qwen-code/pull/13602)) by @ZedingZhang
|
||||
- fix(serve): name Hosted recovery-refusal branches in cold-load 409s ([#13276](https://github.com/QwenLM/qwen-code/pull/13276)) by @wenshao
|
||||
- fix(cli): bound managed function-hook module evaluation and keep hold-fenced owners recoverable ([#13243](https://github.com/QwenLM/qwen-code/pull/13243)) by @wenshao
|
||||
- Fixes a managed-agent publication issue by ensuring placement is selected before retention, quota, and session writer locks. ([#13894](https://github.com/QwenLM/qwen-code/pull/13894)) by @doudouOUC
|
||||
- fix(managed-agent): settle provably never-started dispatches at session open (#13801) ([#13917](https://github.com/QwenLM/qwen-code/pull/13917)) by @wenshao
|
||||
- Fixes /review Test Plan checks to correctly parse comma-grouped test counts such as 2,239 passed. ([#13881](https://github.com/QwenLM/qwen-code/pull/13881)) by @wenshao
|
||||
- Fixes managed-agent config and API behavior with input-size aggregate limits, removed dead config, corrected defaults, and better duration handling. ([#13335](https://github.com/QwenLM/qwen-code/pull/13335)) by @wenshao
|
||||
- Localizes Web Shell voice dictation feedback in English and Simplified Chinese, adds dismissible error and no-speech messages, and restores the microphone for retry. ([#13938](https://github.com/QwenLM/qwen-code/pull/13938)) by @ytahdn
|
||||
- Repairs CLI CI tests by draining wake park before teardown and restoring shell gate guard expectations. ([#13959](https://github.com/QwenLM/qwen-code/pull/13959)) by @qwen-code-dev-bot
|
||||
- fix(lsp): load .lsp.json files saved with a UTF-8 BOM ([#13946](https://github.com/QwenLM/qwen-code/pull/13946)) by @Shizoqua
|
||||
- fix(desktop): preserve bundled runtime integrity in AppImage ([#13695](https://github.com/QwenLM/qwen-code/pull/13695)) by @hahahahahayesyeseys
|
||||
- fix(release): anchor desktop release notes at the previous desktop release ([#13960](https://github.com/QwenLM/qwen-code/pull/13960)) by @Jay0130-a
|
||||
- test(web-shell): re-measure the transcript entry ceiling (#13982) ([#13985](https://github.com/QwenLM/qwen-code/pull/13985)) by @qwen-code-dev-bot
|
||||
- fix(cli): report a spawn failure of the CLI child instead of exiting silently ([#13077](https://github.com/QwenLM/qwen-code/pull/13077)) by @drakeo338
|
||||
- fix(ci): double the sdk-java MariaDB lane's per-host lock budget (#13989) ([#13991](https://github.com/QwenLM/qwen-code/pull/13991)) by @qwen-code-dev-bot
|
||||
- fix(cli): drop the stale gate-close from the restored-shell-gate test ([#13983](https://github.com/QwenLM/qwen-code/pull/13983)) by @yiliang114
|
||||
- fix(core): clean the folded cause before getErrorMessage caps the retained subagent error text ([#14008](https://github.com/QwenLM/qwen-code/pull/14008)) by @yiliang114
|
||||
- fix(cli): load TOML custom commands saved with a UTF-8 BOM ([#13945](https://github.com/QwenLM/qwen-code/pull/13945)) by @Shizoqua
|
||||
- fix(ci): recover reused runner checkouts and Java lock waits ([#14017](https://github.com/QwenLM/qwen-code/pull/14017)) by @yiliang114
|
||||
- fix(core): strip a leading BOM before converting a TOML command ([#13975](https://github.com/QwenLM/qwen-code/pull/13975)) by @Bug-killer-prog
|
||||
- fix(cli): give bare ↑/↓ back to input history in Virtualized History ([#14016](https://github.com/QwenLM/qwen-code/pull/14016)) by @chiga0
|
||||
- fix(web-shell): hide the discontinued built-in OAuth model in Settings ([#13971](https://github.com/QwenLM/qwen-code/pull/13971)) by @Bug-killer-prog
|
||||
- fix(cli): window the OpenTUI transcript to fix the blank-screen resume ([#13669](https://github.com/QwenLM/qwen-code/pull/13669)) by @chiga0
|
||||
- fix(cli): keep the OpenTUI composer caret steady ([#13693](https://github.com/QwenLM/qwen-code/pull/13693)) by @chiga0
|
||||
- fix(managed-agent): share title blankness and pin the lease-renewal clamp ([#13979](https://github.com/QwenLM/qwen-code/pull/13979)) by @yiliang114
|
||||
- fix(managed-agent): count http/function result Hooks at child admission ([#13978](https://github.com/QwenLM/qwen-code/pull/13978)) by @yiliang114
|
||||
- fix(cli): stop OpenTUI slash commands reading their own dispatch as busy ([#12960](https://github.com/QwenLM/qwen-code/pull/12960)) by @MohammedAlkindi
|
||||
- fix(cli): recognize markdown fences only on matching lines ([#13331](https://github.com/QwenLM/qwen-code/pull/13331)) by @GoldArowana
|
||||
- fix(sdk): strip terminal escapes from plaintext tool previews ([#12751](https://github.com/QwenLM/qwen-code/pull/12751)) by @sxh313
|
||||
- fix(cli): split comma-separated --include-tools/--exclude-tools in mcp add ([#12985](https://github.com/QwenLM/qwen-code/pull/12985)) by @yanyukang2003-ux
|
||||
- fix(web-shell): Ignore standalone whitespace assistant chunks ([#14031](https://github.com/QwenLM/qwen-code/pull/14031)) by @ytahdn
|
||||
- fix(managed-agent): order a sender's commands by commit sequence, and make the member restart IT interrupt a live wait ([#14014](https://github.com/QwenLM/qwen-code/pull/14014)) by @wenshao
|
||||
- fix(desktop): let Web Shell receive OS file drops ([#14053](https://github.com/QwenLM/qwen-code/pull/14053)) by @pomelo-nwu
|
||||
- fix(managed-agent): numeric parity and family-list hoisting for publication families, with end-to-end admission and wedge witnesses ([#13815](https://github.com/QwenLM/qwen-code/pull/13815)) by @wenshao
|
||||
- fix(cli): reject batch plan targets that alias the same path ([#14055](https://github.com/QwenLM/qwen-code/pull/14055)) by @yiliang114
|
||||
- fix(managed-agent): close shell profiles after native file calls ([#14045](https://github.com/QwenLM/qwen-code/pull/14045)) by @doudouOUC
|
||||
- fix(managed-agent): recover lost lifecycle detach replies ([#14039](https://github.com/QwenLM/qwen-code/pull/14039)) by @doudouOUC
|
||||
- fix(serve): resume committed file results after runtime retirement ([#14059](https://github.com/QwenLM/qwen-code/pull/14059)) by @doudouOUC
|
||||
- fix(release): raise the npm package unpacked-size budget to 160 MiB ([#14066](https://github.com/QwenLM/qwen-code/pull/14066)) by @qwen-code-dev-bot
|
||||
- test(managed-agent): widen process-death observation windows in the operator recovery probe (#14072) ([#14079](https://github.com/QwenLM/qwen-code/pull/14079)) by @qwen-code-dev-bot
|
||||
- fix(managed-agent): passively reattach child relay sessions after control-plane restart ([#14020](https://github.com/QwenLM/qwen-code/pull/14020)) by @yiliang114
|
||||
- fix(desktop): keep desktop-latest release notes pointing at the current release ([#13900](https://github.com/QwenLM/qwen-code/pull/13900)) by @shpaker
|
||||
- test(managed-agent): handle the procfs exit race in the operator recovery probe ([#14090](https://github.com/QwenLM/qwen-code/pull/14090)) by @wenshao
|
||||
- test(serve): widen hosted approval inline-limit windows for CI contention (#14084) ([#14091](https://github.com/QwenLM/qwen-code/pull/14091)) by @qwen-code-dev-bot
|
||||
- fix(ci): raise the Hosted MySQL fork and step budgets past the L4 class (#14125) ([#14128](https://github.com/QwenLM/qwen-code/pull/14128)) by @qwen-code-dev-bot
|
||||
- test(managed-agent): Pin the history response size boundary ([#14111](https://github.com/QwenLM/qwen-code/pull/14111)) by @loglapa
|
||||
- fix(cli): bound short OpenTUI scroll dialogs (MYWO-3, MYWO-7) ([#14034](https://github.com/QwenLM/qwen-code/pull/14034)) by @chiga0
|
||||
- fix(managed-agent): Repair the Hosted storage-guard fixtures red since #14069 ([#14131](https://github.com/QwenLM/qwen-code/pull/14131)) by @yiliang114
|
||||
- fix(acp): preserve cancellation intent across session recovery ([#13436](https://github.com/QwenLM/qwen-code/pull/13436)) by @yiliang114
|
||||
- test(managed-agent): register storage for Hosted admission gates ([#14154](https://github.com/QwenLM/qwen-code/pull/14154)) by @yiliang114
|
||||
- test(managed-agent): stabilize retraction snapshot verification ([#14151](https://github.com/QwenLM/qwen-code/pull/14151)) by @yiliang114
|
||||
- fix(managed-agent): settle journal-only parked Harness turns on takeover (#13847) ([#14078](https://github.com/QwenLM/qwen-code/pull/14078)) by @wenshao
|
||||
- test(managed-agent): wait for the published Snapshot before naming its Items ([#14156](https://github.com/QwenLM/qwen-code/pull/14156)) by @yiliang114
|
||||
- fix(managed-agent): Wire OSS capture budgets into Hosted sessions ([#14124](https://github.com/QwenLM/qwen-code/pull/14124)) by @doudouOUC
|
||||
- test(managed-agent): cover coordinator-only Hosted restart ([#14155](https://github.com/QwenLM/qwen-code/pull/14155)) by @yiliang114
|
||||
- fix(build): re-apply the ink patch when install skipped lifecycle scripts ([#14170](https://github.com/QwenLM/qwen-code/pull/14170)) by @yiliang114
|
||||
- fix(cli): preserve Unicode queries in at completion ([#13866](https://github.com/QwenLM/qwen-code/pull/13866)) by @dakjdakd
|
||||
- fix(sdk-python): resolve Windows command shims on the effective PATH ([#13862](https://github.com/QwenLM/qwen-code/pull/13862)) by @dakjdakd
|
||||
- fix(cli): gate non-interactive /update installs on enableAutoUpdate ([#13665](https://github.com/QwenLM/qwen-code/pull/13665)) by @fengyue-xve
|
||||
- fix(core): preserve fuzzy edit line boundaries ([#13484](https://github.com/QwenLM/qwen-code/pull/13484)) by @GoldArowana
|
||||
- fix(sdk): release reconnect transports created after disposal ([#13864](https://github.com/QwenLM/qwen-code/pull/13864)) by @dakjdakd
|
||||
- fix(sdk-java): get the MariaDB lane past #13996's schema, recovery IT and baseline witness ([#14177](https://github.com/QwenLM/qwen-code/pull/14177)) by @wenshao
|
||||
- fix(core): resolve ${CLAUDE_PLUGIN_ROOT} in plugin markdown without hooks ([#14194](https://github.com/QwenLM/qwen-code/pull/14194)) by @yiliang114
|
||||
- fix(ci): repair Java backlog and schema/Hosted test failures ([#14178](https://github.com/QwenLM/qwen-code/pull/14178)) by @yiliang114
|
||||
- fix(ci): re-pin Java gate CI tests to the daily-trigger shape (#14223) ([#14227](https://github.com/QwenLM/qwen-code/pull/14227)) by @qwen-code-dev-bot
|
||||
- fix(sdk-java): restore session-root materialization in acquireWriter (#14224) ([#14228](https://github.com/QwenLM/qwen-code/pull/14228)) by @qwen-code-dev-bot
|
||||
- fix(core): keep Artifact guidance out of record_artifact description ([#14206](https://github.com/QwenLM/qwen-code/pull/14206)) by @zjgzx1988
|
||||
- fix(managed-agent): settle the monitor loop when its resume finds a settled record ([#14074](https://github.com/QwenLM/qwen-code/pull/14074)) by @wenshao
|
||||
- fix(ci): repair daily failure reporting and contract guards ([#14238](https://github.com/QwenLM/qwen-code/pull/14238)) by @yiliang114
|
||||
- fix(ci): repin the sdk-java test job's MAVEN_ARGS count for the checkstyle gate (#14247) ([#14258](https://github.com/QwenLM/qwen-code/pull/14258)) by @qwen-code-dev-bot
|
||||
- fix(sdk-java): Order the MQ3 gate's imports for the new checkstyle rules ([#14253](https://github.com/QwenLM/qwen-code/pull/14253)) by @wenshao
|
||||
- fix(core): select the DashScope provider for Model Studio workspace domains ([#14245](https://github.com/QwenLM/qwen-code/pull/14245)) by @qqqys
|
||||
- fix(managed-agent): close the critical R2 review findings on #12692 ([#13325](https://github.com/QwenLM/qwen-code/pull/13325)) by @wenshao
|
||||
- fix(core): surface the provider request id on bodyless API errors ([#14133](https://github.com/QwenLM/qwen-code/pull/14133)) by @yiliang114
|
||||
- fix(managed-agent): hand one run's session messages over in send order ([#14235](https://github.com/QwenLM/qwen-code/pull/14235)) by @wenshao
|
||||
- fix(Managed agent): settle never-started background dispatches in the live turn ([#14021](https://github.com/QwenLM/qwen-code/pull/14021)) by @yiliang114
|
||||
- fix(core): keep the git snapshot out of the system prompt ([#14255](https://github.com/QwenLM/qwen-code/pull/14255)) by @qqqys
|
||||
- fix(ci): gate Desktop and Java SDK releases on checks of the release commit ([#14259](https://github.com/QwenLM/qwen-code/pull/14259)) by @yiliang114
|
||||
- fix(serve): align Hosted team delivery answers ([#14267](https://github.com/QwenLM/qwen-code/pull/14267)) by @yiliang114
|
||||
- fix(ci): drain Hosted test turns and repair nightly browser checks ([#14275](https://github.com/QwenLM/qwen-code/pull/14275)) by @yiliang114
|
||||
- fix(cli): close post-merge takeover findings from the #13083 review ([#13188](https://github.com/QwenLM/qwen-code/pull/13188)) by @wenshao
|
||||
- fix(vscode): move conversation payloads out of globalState ([#14285](https://github.com/QwenLM/qwen-code/pull/14285)) by @yiliang114
|
||||
|
||||
#### Documentation
|
||||
|
||||
- Adds design documentation for managed-agent team member commands, plan approval, and shutdown on the Hosted path. ([#13912](https://github.com/QwenLM/qwen-code/pull/13912)) by @wenshao
|
||||
- docs(managed-agent): Resolve the EventTransport §5.1 LiteTopic verification ledger ([#13927](https://github.com/QwenLM/qwen-code/pull/13927)) by @wenshao
|
||||
- docs(managed-agent): Resolve the EventTransport §11 routing and wake-carrier questions ([#13933](https://github.com/QwenLM/qwen-code/pull/13933)) by @wenshao
|
||||
- Adds documentation planning the EventTransport MQ2 Java producer/consumer wiring into four independently reviewable sub-slices. ([#13939](https://github.com/QwenLM/qwen-code/pull/13939)) by @wenshao
|
||||
- docs(sdk): clarify empty core-tool allowlists across SDKs ([#13949](https://github.com/QwenLM/qwen-code/pull/13949)) by @Zhangchenhao-111
|
||||
- docs: record local tool visibility comparison and verification steps ([#13767](https://github.com/QwenLM/qwen-code/pull/13767)) by @yiliang114
|
||||
- docs(readme): simplify evaluation overview and refine trend chart ([#14093](https://github.com/QwenLM/qwen-code/pull/14093)) by @pomelo-nwu
|
||||
- docs(cli): stop naming Terminal.app in the arrowKeysScrollTranscript description ([#14092](https://github.com/QwenLM/qwen-code/pull/14092)) by @yiliang114
|
||||
- docs(runtime-broker): design Workspace-resident Managed Tool Runtime ([#14214](https://github.com/QwenLM/qwen-code/pull/14214)) by @doudouOUC
|
||||
- docs: remind to update workflow meta-tests when changing CI workflows ([#14239](https://github.com/QwenLM/qwen-code/pull/14239)) by @yiliang114
|
||||
- docs(design): drop stray conflict markers from the actor-roles design ([#14270](https://github.com/QwenLM/qwen-code/pull/14270)) by @doudouOUC
|
||||
|
||||
#### Internal Changes
|
||||
|
||||
- Adds internal FG7 fault-gate tests that verify managed-agent channel behavior during control-plane and provider failures. ([#13839](https://github.com/QwenLM/qwen-code/pull/13839)) by @wenshao
|
||||
- Improves test stability for child-Workspace Git layout validation by avoiding a possible directory-name collision. ([#13844](https://github.com/QwenLM/qwen-code/pull/13844)) by @qwen-code-dev-bot
|
||||
- test(integration): sync the updatedInput ask approval on the dialog, not the tool row (#13849) ([#13855](https://github.com/QwenLM/qwen-code/pull/13855)) by @qwen-code-dev-bot
|
||||
- Raises the Web Shell transcript entry size ceiling after selection-copy strings increased bundle size, with no production behavior change. ([#13964](https://github.com/QwenLM/qwen-code/pull/13964)) by @qwen-code-dev-bot
|
||||
- test(runtime-broker): budget the 1 MiB tool round trips past the request deadline ([#13990](https://github.com/QwenLM/qwen-code/pull/13990)) by @yiliang114
|
||||
- test(managed-agent): clean up recovery fixtures after probe failures ([#14018](https://github.com/QwenLM/qwen-code/pull/14018)) by @yiliang114
|
||||
- test(managed-agent): stabilize publication and API contract fixtures ([#14012](https://github.com/QwenLM/qwen-code/pull/14012)) by @yiliang114
|
||||
- test(managed-agent): land the deferred findings from the definition-execution review ([#13980](https://github.com/QwenLM/qwen-code/pull/13980)) by @yiliang114
|
||||
- test(core): cover unfinished invocation guard before turn reclamation ([#13257](https://github.com/QwenLM/qwen-code/pull/13257)) by @jayfeng7
|
||||
- refactor(core): one record/array gate for file-history snapshot payloads ([#13981](https://github.com/QwenLM/qwen-code/pull/13981)) by @yiliang114
|
||||
- test(managed-agent): deflake the transient verification retry rescan (#14037) ([#14042](https://github.com/QwenLM/qwen-code/pull/14042)) by @qwen-code-dev-bot
|
||||
- Add SerpApi to web search MCP services list ([#13104](https://github.com/QwenLM/qwen-code/pull/13104)) by @pulkitchowdry
|
||||
- test(managed-agent): fix the shell lifecycle recovery journal-head assertions (#14103) ([#14108](https://github.com/QwenLM/qwen-code/pull/14108)) by @qwen-code-dev-bot
|
||||
- test(serve): widen hosted recovery waitFor windows for CI contention (#14101) ([#14107](https://github.com/QwenLM/qwen-code/pull/14107)) by @qwen-code-dev-bot
|
||||
- test(managed-agent): Run the EventTransport fault rows against a real RocketMQ broker (MQ2d) ([#14109](https://github.com/QwenLM/qwen-code/pull/14109)) by @wenshao
|
||||
- test(integration): Cover a completing write_file tool call over ACP ([#14144](https://github.com/QwenLM/qwen-code/pull/14144)) by @yiliang114
|
||||
- test(managed-agent): retry the team-lead hosted boot past the free-port race ([#14153](https://github.com/QwenLM/qwen-code/pull/14153)) by @yiliang114
|
||||
- refactor(core): give the Code Mode tool-result stamp a single owner ([#13666](https://github.com/QwenLM/qwen-code/pull/13666)) by @fengyue-xve
|
||||
- test(ci): pin the main-failure watcher's whole job condition ([#14233](https://github.com/QwenLM/qwen-code/pull/14233)) by @wenshao
|
||||
- chore: adopt qwen-code-checks.xml and fix all checkstyle violations ([#14186](https://github.com/QwenLM/qwen-code/pull/14186)) by @doudouOUC
|
||||
- test(managed-agent): Run the EventTransport fault matrix against two nodes (MQ3) ([#14242](https://github.com/QwenLM/qwen-code/pull/14242)) by @wenshao
|
||||
- test(cli): skip the chmod-based tests where permission bits do not bind ([#14162](https://github.com/QwenLM/qwen-code/pull/14162)) by @wenshao
|
||||
- refactor(runtime-broker): name the local-process kind and Workspace isolation policy ([#14254](https://github.com/QwenLM/qwen-code/pull/14254)) by @doudouOUC
|
||||
- test(managed-agent): gate G3 Step 3 recovery, model-round takeover and control-plane fencing ([#14181](https://github.com/QwenLM/qwen-code/pull/14181)) by @wenshao
|
||||
- diagnostics(ci): capture RocketMQ latency stages separately ([#14241](https://github.com/QwenLM/qwen-code/pull/14241)) by @yiliang114
|
||||
- refactor(managed-agent): Reuse Apache Commons utilities ([#14280](https://github.com/QwenLM/qwen-code/pull/14280)) by @doudouOUC
|
||||
|
||||
### New Contributors
|
||||
|
||||
- @ZedingZhang made their first contribution in [#13602](https://github.com/QwenLM/qwen-code/pull/13602)
|
||||
- @Zhangchenhao-111 made their first contribution in [#13949](https://github.com/QwenLM/qwen-code/pull/13949)
|
||||
- @hahahahahayesyeseys made their first contribution in [#13695](https://github.com/QwenLM/qwen-code/pull/13695)
|
||||
- @Jay0130-a made their first contribution in [#13960](https://github.com/QwenLM/qwen-code/pull/13960)
|
||||
- @drakeo338 made their first contribution in [#13077](https://github.com/QwenLM/qwen-code/pull/13077)
|
||||
- @MohammedAlkindi made their first contribution in [#12960](https://github.com/QwenLM/qwen-code/pull/12960)
|
||||
- @jayfeng7 made their first contribution in [#13257](https://github.com/QwenLM/qwen-code/pull/13257)
|
||||
- @yanyukang2003-ux made their first contribution in [#12985](https://github.com/QwenLM/qwen-code/pull/12985)
|
||||
- @shpaker made their first contribution in [#13900](https://github.com/QwenLM/qwen-code/pull/13900)
|
||||
- @pulkitchowdry made their first contribution in [#13104](https://github.com/QwenLM/qwen-code/pull/13104)
|
||||
- @loglapa made their first contribution in [#14111](https://github.com/QwenLM/qwen-code/pull/14111)
|
||||
- @dakjdakd made their first contribution in [#13866](https://github.com/QwenLM/qwen-code/pull/13866)
|
||||
- @fengyue-xve made their first contribution in [#13665](https://github.com/QwenLM/qwen-code/pull/13665)
|
||||
|
||||
**Full Changelog**: https://github.com/QwenLM/qwen-code/compare/v0.25.1...v0.25.2
|
||||
|
||||
## [0.25.1](https://github.com/QwenLM/qwen-code/releases/tag/v0.25.1) - 2026-10-11
|
||||
|
||||
### Highlights
|
||||
|
|
|
|||
|
|
@ -110,6 +110,8 @@ To install dependencies defined in `package.json` as well as root dependencies,
|
|||
corepack pnpm install --frozen-lockfile
|
||||
```
|
||||
|
||||
The install also applies `patches/ink+7.0.3.patch` through the root `postinstall` (`patch-package`). `packages/cli` compiles against the `ink` frame-controller types that patch adds, and no published `ink` release exports them, so an install that skipped lifecycle scripts (`--ignore-scripts`, or `ignore-scripts=true` in your npm/pnpm config) leaves the build failing with many `Module 'ink' has no exported member ...` errors. `npm run build` re-applies the patch when it is missing; to apply it by hand, run `npm run postinstall`.
|
||||
|
||||
To build the entire project (all packages):
|
||||
|
||||
```bash
|
||||
|
|
|
|||
29
README.md
29
README.md
|
|
@ -157,39 +157,18 @@ If you know Claude Code, you already know Qwen Code — and then some. We've put
|
|||
|
||||
## Qwen Code Evaluation
|
||||
|
||||
### Evaluation Configuration
|
||||
On **SWE-bench Verified**, seven Qwen Code versions scored **76.40%–77.80%** on average using the same Qwen 3.7 Max model (500 tasks, three trials per version).
|
||||
|
||||
| Configuration | Value |
|
||||
| ----------------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| Dataset | `princeton-nlp/SWE-bench_Verified`, 500 cases |
|
||||
| Runs | 3 trials per version, 1,500 jobs per version; 7 Qwen Code versions |
|
||||
| Model | `Qwen 3.7 Max` |
|
||||
| Sampling | `temperature=1`, `top_p=0.95` |
|
||||
| Thinking / Reasoning | `interleaved_thinking=true`, `thinking_type=adaptive`, `reasoning_effort=high` |
|
||||
| Context / Output Limits | `context_1m=true`, `model_context_length=1,000,000`, `max_tokens=64,000`, `model_max_tokens=65,535` |
|
||||
| Harness Limits | `max_iterations=500`, `runtime_timeout_sec=7,200`, `agent_timeout_multiplier=3.0` |
|
||||

|
||||
|
||||
### Scores by Qwen Code Version
|
||||
|
||||
| Version | Avg Score | Pass Rate | pass@3 | pass^3 |
|
||||
| ------- | ---------: | --------: | -----: | -----: |
|
||||
| 0.0.14 | **77.80%** | 84.6% | 84.6% | 69.2% |
|
||||
| 0.1.0 | **76.73%** | 84.6% | 84.6% | 68.0% |
|
||||
| 0.5.0 | **76.60%** | 83.0% | 83.0% | 68.4% |
|
||||
| 0.10.0 | **76.47%** | 84.4% | 84.4% | 67.4% |
|
||||
| 0.15.0 | **77.67%** | 84.6% | 84.6% | 68.6% |
|
||||
| 0.19.0 | **76.40%** | 85.0% | 85.0% | 65.8% |
|
||||
| 0.22.0 | **77.33%** | 84.4% | 84.4% | 68.0% |
|
||||
|
||||
### Avg Score Trend
|
||||
|
||||

|
||||
[Full results and evaluation configuration →](./docs/evaluation.md)
|
||||
|
||||
## Ecosystem
|
||||
|
||||
- [**Qwen Code Desktop**](https://github.com/QwenLM/qwen-code/releases/tag/desktop-latest) — Official desktop app for macOS, Windows, and Linux
|
||||
- [**AionUi**](https://github.com/iOfficeAI/AionUi) — A modern GUI for command-line AI tools including Qwen Code
|
||||
- [**Gemini CLI Desktop**](https://github.com/Piebald-AI/gemini-cli-desktop) — A cross-platform desktop/web/mobile UI for Qwen Code
|
||||
- [**qwen-audio-agent**](https://github.com/QwenAudio/qwen-audio-agent) — Realtime full-duplex voice frontend for Qwen Code over ACP — talk hands-free with barge-in and a local wake word
|
||||
|
||||
- [**🦞 Qwen Code Claw**](https://github.com/openclaw/acpx) — Let other agents (Claude, Codex, etc.) delegate coding tasks to Qwen Code via ACP. Paste this prompt into your agent:
|
||||
|
||||
|
|
|
|||
69
deploy/k2-resident/README.md
Normal file
69
deploy/k2-resident/README.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# Resident Workspace configuration
|
||||
|
||||
[English](README.md) | [简体中文](README.zh-CN.md)
|
||||
|
||||
This first-release configuration retains one Pod/PVC per tenant/Workspace.
|
||||
It supports Read, Write and Edit through the public files profile. Session close
|
||||
settles and revokes the original context; the next Session retains the same
|
||||
files and runtime. It does not install a node collector or automatically delete
|
||||
or reuse storage. This directory has not been deployed or accepted in ACK.
|
||||
|
||||
Use `application.example.yaml` as an additional Spring configuration file.
|
||||
Supply the server's existing database, Hosted Harness, Session store, Broker
|
||||
token and credential-encryption settings separately. The Kubernetes token and CA
|
||||
settings name local files read by the server; credentials do not belong in this
|
||||
template or the worker Workspace.
|
||||
|
||||
Keep the Session store's global `workspace-id` equal to the Broker's global
|
||||
`workspace-id`, as in the example. The existing Hosted startup contract requires
|
||||
this match; each public Workspace still resolves its own registered identity.
|
||||
|
||||
Before enabling the mapping, register the exact CSI registration with the
|
||||
existing registration operator and register the matching tenant/Workspace,
|
||||
generation and storage in the Workspace registry. Use `managed-runtime-tools/1`
|
||||
and `preapproved-workspace-tools/1` as the Workspace config/policy, then grant the
|
||||
trusted actor OPERATOR or OWNER. Each physical volume may appear in only one
|
||||
resident mapping. A registration or a caller-supplied Workspace ID alone does
|
||||
not enable CSI execution.
|
||||
|
||||
Render the exact storage/namespace protection objects with the existing Java
|
||||
protection operator, passing `<reviewed-registration-json> <policy-name>
|
||||
<binding-name>`. This prints a Kubernetes JSON List and
|
||||
does not contact the cluster or database. From the server package, invoke the
|
||||
built operator with:
|
||||
|
||||
```bash
|
||||
java -Dloader.main=com.alibaba.qwen.code.managedagent.service.WorkspaceCsiProtectionMain \
|
||||
-cp target/qwen-managed-agent-server-0.1.0-alpha-csi-runtime.jar \
|
||||
org.springframework.boot.loader.launch.PropertiesLauncher \
|
||||
/absolute/path/registration.json resident-protection resident-binding
|
||||
```
|
||||
|
||||
After separately authorized installation, pin its actual namespace/policy/binding
|
||||
UIDs and generations. Select an immutable image digest containing Node.js 22 or
|
||||
later and the built CLI. The CSI file path uses Node filesystem APIs and reads
|
||||
Linux `/proc/self/mountinfo`, `/proc/self/fd` and the volume's
|
||||
`/sys/dev/block/<major>:<minor>/device/serial`; these must be available to UID 1000. The command contains only the executable and CLI path;
|
||||
the provisioner appends `managed-runtime-worker --container-boot` and the boot
|
||||
file path. Optional artifact ConfigMaps must be immutable and explicitly pinned.
|
||||
|
||||
The server must reach the Kubernetes API and the original Pod's worker endpoint.
|
||||
The worker runs as UID/GID 1000, with a read-only root filesystem, no added Linux
|
||||
capabilities or service-account token, its fixed PVC at `/workspace`, and its
|
||||
temporary directory in an emptyDir. The Pod has `restartPolicy: Never`. A
|
||||
missing, restarted or replaced original Pod keeps admission closed; this version
|
||||
does not recreate or adopt a replacement.
|
||||
|
||||
Create and close Sessions using the existing public Workspace Session APIs.
|
||||
Only one unfinished Session is admitted. Close must succeed before another
|
||||
Session can begin. A release timeout or unknown outcome retains the reservation
|
||||
and blocks handoff. The selected cwd is fixed for each Session; choose another
|
||||
cwd at the next Session's creation. Shell, MCP, Hooks, child Sessions sharing the
|
||||
runtime and in-place cwd changes are unavailable in this version.
|
||||
|
||||
Resident resources continue to incur normal Pod/node/storage costs after
|
||||
Session close. No live cloud installation, paid resource creation or manual
|
||||
physical teardown is authorized by this template. Use a separately approved
|
||||
deployment/maintenance plan for those actions. See the
|
||||
[design](../../docs/design/2026-10-14-k2-resident-workspace.md) for the failure and
|
||||
acceptance boundaries.
|
||||
57
deploy/k2-resident/README.zh-CN.md
Normal file
57
deploy/k2-resident/README.zh-CN.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# 常驻 Workspace 配置
|
||||
|
||||
[English](README.md) | [简体中文](README.zh-CN.md)
|
||||
|
||||
首版配置为每个租户/Workspace 保留一个 Pod/PVC,通过公开 files 配置支持
|
||||
Read、Write 和 Edit。Session 关闭会结算并撤销原上下文,下一个 Session 保留
|
||||
同一文件和运行环境。此方案不安装节点 collector,也不自动删除或复用存储。
|
||||
本目录尚未在 ACK 部署或验收。
|
||||
|
||||
将 `application.example.yaml` 作为额外的 Spring 配置文件,另外提供现有数据库、
|
||||
Hosted Harness、Session store、Broker token 和凭据加密配置。Kubernetes token
|
||||
和 CA 配置指向服务端读取的本地文件,凭据不应写入此模板或 worker Workspace。
|
||||
|
||||
Session store 和 Broker 的全局 `workspace-id` 必须像示例中一样保持相等,这是现有
|
||||
Hosted 启动契约的要求;各公开 Workspace 仍解析自己的注册身份。
|
||||
|
||||
启用映射前,使用现有注册 operator 登记精确 CSI registration,并在 Workspace
|
||||
registry 登记匹配的租户/Workspace、代数和存储。Workspace config/policy 分别
|
||||
使用 `managed-runtime-tools/1` 和 `preapproved-workspace-tools/1`,再给可信
|
||||
actor 授予 OPERATOR 或 OWNER。每个物理卷只能出现在一条常驻映射中。单有
|
||||
注册信息或调用者提供的 Workspace ID 不会启用 CSI 执行。
|
||||
|
||||
使用现有 Java 保护 operator 的 `<reviewed-registration-json>
|
||||
<policy-name> <binding-name>` 命令生成精确存储及命名空间保护对象。命令只打印
|
||||
Kubernetes JSON List,不访问集群或数据库。在 server 包目录中调用已构建的
|
||||
operator:
|
||||
|
||||
```bash
|
||||
java -Dloader.main=com.alibaba.qwen.code.managedagent.service.WorkspaceCsiProtectionMain \
|
||||
-cp target/qwen-managed-agent-server-0.1.0-alpha-csi-runtime.jar \
|
||||
org.springframework.boot.loader.launch.PropertiesLauncher \
|
||||
/absolute/path/registration.json resident-protection resident-binding
|
||||
```
|
||||
|
||||
在另外授权安装后,钉住实际 namespace、policy、binding 的 UID 和代数。使用不可变
|
||||
镜像摘要,镜像包含 Node.js 22 或更新版本及构建后的 CLI。CSI 文件路径使用 Node
|
||||
文件系统 API,并读取 Linux `/proc/self/mountinfo`、`/proc/self/fd` 和卷的
|
||||
`/sys/dev/block/<major>:<minor>/device/serial`;UID 1000 必须能够访问这些位置。
|
||||
command 只包含执行程序和 CLI 路径;provisioner
|
||||
会追加 `managed-runtime-worker --container-boot` 和 boot 文件路径。可选产物
|
||||
ConfigMap 必须不可变并显式钉住身份。
|
||||
|
||||
服务端需要访问 Kubernetes API 和原 Pod 的 worker 端点。worker 以 UID/GID
|
||||
1000 运行,根文件系统只读,不添加 Linux capabilities,不带 service-account
|
||||
token,固定 PVC 挂在 `/workspace`,临时目录使用 emptyDir。Pod 使用
|
||||
`restartPolicy: Never`。原 Pod 缺失、重启或被替换均保持准入关闭;本版不会
|
||||
重建或接管替代 Pod。
|
||||
|
||||
使用现有公开 Workspace Session API 创建和关闭 Session,同一 Workspace 只
|
||||
准入一个未完成 Session。关闭成功后才能启动下一个。释放超时或结果未知会保留
|
||||
reservation 并阻止切换。每个 Session 的 cwd 固定,可在下一 Session 创建时
|
||||
选择其他目录。本版不支持 Shell、MCP、Hooks、共享运行环境的子 Session 或
|
||||
原地 cwd 变更。
|
||||
|
||||
Session 关闭后常驻资源继续产生正常的 Pod、节点和存储费用。此模板不授权云上
|
||||
安装、付费资源创建或手工物理销毁;这些动作使用另外批准的部署或维护方案。
|
||||
失败和验收边界见[设计](../../docs/design/2026-10-14-k2-resident-workspace.zh-CN.md)。
|
||||
51
deploy/k2-resident/application.example.yaml
Normal file
51
deploy/k2-resident/application.example.yaml
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Supply reviewed identities and merge this with the server's normal configuration.
|
||||
# This file is a template, not an applied deployment or an ACK acceptance result.
|
||||
qwen:
|
||||
managed-agent:
|
||||
harness:
|
||||
workspace-files-enabled: true
|
||||
workspace-shell-enabled: false
|
||||
session-store:
|
||||
workspace-id: 'resident-default'
|
||||
runtime-broker:
|
||||
enabled: true
|
||||
provisioner: 'kubernetes-workspace'
|
||||
isolation-class: 'workspace'
|
||||
trusted-local-reboot-recovery: false
|
||||
verified-workspace-recovery-enabled: false
|
||||
child-workspaces-enabled: false
|
||||
workspace-mounts: []
|
||||
workspace-id: 'resident-default'
|
||||
workspace-cwd: '/workspace'
|
||||
resident-kubernetes-api-url: '${RESIDENT_KUBERNETES_API_URL}'
|
||||
resident-kubernetes-token-file: '${RESIDENT_KUBERNETES_TOKEN_FILE}'
|
||||
resident-kubernetes-ca-file: '${RESIDENT_KUBERNETES_CA_FILE}'
|
||||
resident-workspaces:
|
||||
- workspace-id: '${RESIDENT_WORKSPACE_ID}'
|
||||
workspace-generation: 1
|
||||
registration:
|
||||
tenant-id: '${RESIDENT_TENANT_ID}'
|
||||
storage-id: '${RESIDENT_STORAGE_ID}'
|
||||
cluster-domain: '${RESIDENT_CLUSTER_DOMAIN}'
|
||||
namespace: '${RESIDENT_NAMESPACE}'
|
||||
pvc-name: '${RESIDENT_PVC_NAME}'
|
||||
pvc-uid: '${RESIDENT_PVC_UID}'
|
||||
pv-name: '${RESIDENT_PV_NAME}'
|
||||
pv-uid: '${RESIDENT_PV_UID}'
|
||||
driver: '${RESIDENT_CSI_DRIVER}'
|
||||
volume-handle: '${RESIDENT_VOLUME_HANDLE}'
|
||||
backend-domain: '${RESIDENT_BACKEND_DOMAIN}'
|
||||
disk-serial: '${RESIDENT_DISK_SERIAL}'
|
||||
mount-root: '/workspace'
|
||||
revision: 1
|
||||
protection:
|
||||
namespace-uid: '${RESIDENT_NAMESPACE_UID}'
|
||||
policy-name: '${RESIDENT_POLICY_NAME}'
|
||||
policy-uid: '${RESIDENT_POLICY_UID}'
|
||||
policy-generation: 1
|
||||
binding-name: '${RESIDENT_POLICY_BINDING_NAME}'
|
||||
binding-uid: '${RESIDENT_POLICY_BINDING_UID}'
|
||||
binding-generation: 1
|
||||
image: '${RESIDENT_WORKER_IMAGE_DIGEST}'
|
||||
command: ['node', '/opt/qwen/dist/cli.js']
|
||||
artifacts: []
|
||||
13
docs/archive/managed-agent-schema/README.md
Normal file
13
docs/archive/managed-agent-schema/README.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Archived Managed Agent database migrations
|
||||
|
||||
[中文版](README.zh-CN.md)
|
||||
|
||||
These files describe the retired 63-table database at source commit `18f80f56666a347a2b67b2ef98099cc58356b253`. They are historical references and are outside the runtime and test classpaths. The archived Java migrations and upgrade witnesses are not executed by the new environment.
|
||||
|
||||
The only active baseline is [`V1__managed_schema.sql`](../../../packages/managed-agent-server/src/main/resources/db/managed-schema/V1__managed_schema.sql). Managed Server and Runtime Broker consume that same versioned resource and require an empty database or a structurally verified complete 32-table baseline. A legacy, partial, foreign, or history-only catalog is rejected before initialization writes. Reinitialization validates the catalog; it does not backfill or repair it.
|
||||
|
||||
There is no old-data migration or in-place upgrade. Keep the old environment and database for historical reads, stop its new admissions and settle in-flight work before cutover, and create new Sessions only in the new environment. A rollback after new data exists must preserve and serve those records from the new database.
|
||||
|
||||
The archived tests preserve the retired migration algorithm's evidence. Active replacements verify fresh initialization, immutable existing rows, typed variant columns, Hook uniqueness and indexed admission, and rejection of old catalogs. The duplicated Broker `schema.sql` was removed rather than retained as another schema authority.
|
||||
|
||||
The PR #13642 retention V69 SQL and `RuntimeBrokerRetentionLegacyUpgradeTest.java` preserve the former retention-index/receipt upgrade evidence from head `db4c37064b9f34d2aa879b23b0e894fbed0281e1`. They are archived only: the active 32-table V1 baseline supplies the retention indexes, and its replacement test verifies fresh catalog parity and immutable receipts on repeated initialization. V69 is not an active migration or a version reservation.
|
||||
13
docs/archive/managed-agent-schema/README.zh-CN.md
Normal file
13
docs/archive/managed-agent-schema/README.zh-CN.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Managed Agent 数据库历史迁移归档
|
||||
|
||||
[English version](README.md)
|
||||
|
||||
这些文件记录提交 `18f80f56666a347a2b67b2ef98099cc58356b253` 中已停用的 63 表数据库,只供历史参考,不在运行时或测试 classpath 中。归档的 Java 迁移与升级验收不会在新环境执行。
|
||||
|
||||
唯一启用的 baseline 是 [`V1__managed_schema.sql`](../../../packages/managed-agent-server/src/main/resources/db/managed-schema/V1__managed_schema.sql)。Managed Server 与 Runtime Broker 读取同一个版本化资源,仅允许空库或结构验证通过的完整 32 表库。旧库、残缺库、外部表及仅有迁移历史的库,在初始化写入前被拒绝。重复初始化只验证结构,不回填或修复。
|
||||
|
||||
本次不迁移旧数据、不原地升级。旧环境及旧库保留历史读取,切换前停止新请求准入并结算在途工作,新会话只在新环境创建。新库产生数据后,回滚必须保留并从新库提供这些记录。
|
||||
|
||||
归档测试保留旧迁移算法的历史证据,现役替代测试验证空库初始化、已有记录保持不变、有类型的可选字段、Hook 唯一约束及索引准入,以及旧库拒绝。重复的 Broker `schema.sql` 已移除,不再保留第二套结构权威。
|
||||
|
||||
PR #13642 的清理 V69 SQL 与 `RuntimeBrokerRetentionLegacyUpgradeTest.java` 保留 head `db4c37064b9f34d2aa879b23b0e894fbed0281e1` 中原清理索引/receipt 升级证据,仅作为归档。现役 32 表 V1 baseline 提供清理索引,替代测试验证新库 catalog 一致性及重复初始化时 receipt 不变。V69 不是现役 migration,也不构成版本预留。
|
||||
|
|
@ -4,6 +4,9 @@ import com.alibaba.qwen.code.managedagent.store.EventIdentity;
|
|||
import com.alibaba.qwen.code.managedagent.store.EventIdentity.Identity;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.flywaydb.core.api.migration.BaseJavaMigration;
|
||||
import org.flywaydb.core.api.migration.Context;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
|
|
@ -12,8 +15,6 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import org.flywaydb.core.api.migration.BaseJavaMigration;
|
||||
import org.flywaydb.core.api.migration.Context;
|
||||
|
||||
/**
|
||||
* Gives events written before V14 the Item and Part identity that the server
|
||||
|
|
@ -5,6 +5,9 @@ import com.alibaba.qwen.code.managedagent.store.ManagedExtensionRecords.InvalidR
|
|||
import com.alibaba.qwen.code.managedagent.store.ManagedHookRecords;
|
||||
import com.alibaba.qwen.code.managedagent.store.ManagedHookRecords.AdmissionKeys;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.flywaydb.core.api.migration.BaseJavaMigration;
|
||||
import org.flywaydb.core.api.migration.Context;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
|
@ -18,8 +21,6 @@ import java.time.Instant;
|
|||
import java.util.HashSet;
|
||||
import java.util.HexFormat;
|
||||
import java.util.Set;
|
||||
import org.flywaydb.core.api.migration.BaseJavaMigration;
|
||||
import org.flywaydb.core.api.migration.Context;
|
||||
|
||||
/**
|
||||
* Gives the Hook records written before V28 the admission projection that
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
CREATE INDEX idx_runtime_binding_retention
|
||||
ON qwen_runtime_binding (binding_state, last_active_at, binding_id);
|
||||
CREATE INDEX idx_runtime_binding_tenant_state
|
||||
ON qwen_runtime_binding (tenant_id, binding_state);
|
||||
CREATE INDEX idx_workspace_execution_binding
|
||||
ON managed_workspace_execution_lease (binding_id, runtime_generation);
|
||||
CREATE INDEX idx_tool_publication_execution_key
|
||||
ON qwen_tool_publication (execution_key);
|
||||
CREATE INDEX idx_csi_worker_ack_execution
|
||||
ON managed_workspace_csi_worker_ack (execution_call_id_hash);
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue