mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
* feat(live): add visual input, proactive memory and desktop controls * fix(live): preserve upstream checks and finalize migration validation * feat(live): open active configuration from host settings * fix(live): address lifecycle and proactive review findings * feat(live): add subagent controls and diagnostics with review fixes --------- Co-authored-by: 菲兹 <[email protected]> Co-authored-by: Shaojin Wen <[email protected]>
151 lines
6.3 KiB
YAML
151 lines
6.3 KiB
YAML
name: 'Qwen Live Host CI'
|
|
|
|
on:
|
|
pull_request:
|
|
paths:
|
|
- '.github/workflows/live-host.yml'
|
|
- '.github/workflows/live-host-release.yml'
|
|
- 'packages/cli/src/serve/conversations/**'
|
|
- 'packages/cli/src/serve/live/**'
|
|
- 'packages/live-host/**'
|
|
# The protocol parity test couples the qwen-live host copies into
|
|
# this lane; without the path filter a qwen-live-only PR skips it
|
|
# and the first red lands in the merge queue instead.
|
|
- 'packages/qwen-live/src/host/**'
|
|
- 'packages/sdk-typescript/src/daemon/types.ts'
|
|
merge_group:
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: 'read'
|
|
|
|
concurrency:
|
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
test:
|
|
name: 'Live Host (macos-latest)'
|
|
runs-on: 'macos-latest'
|
|
timeout-minutes: 30
|
|
steps:
|
|
- name: 'Check out source'
|
|
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
|
|
|
|
- name: 'Set up Node'
|
|
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
|
|
with:
|
|
node-version-file: '.nvmrc'
|
|
|
|
- name: 'Install dependencies'
|
|
working-directory: 'packages/live-host'
|
|
run: 'npm ci'
|
|
|
|
- name: 'Typecheck Live Host'
|
|
working-directory: 'packages/live-host'
|
|
run: 'npm run typecheck'
|
|
|
|
- name: 'Test Live Host'
|
|
working-directory: 'packages/live-host'
|
|
run: 'npm test'
|
|
|
|
- name: 'Build Live Host'
|
|
working-directory: 'packages/live-host'
|
|
run: 'npm run build'
|
|
|
|
- name: 'Package unsigned Live Host app'
|
|
working-directory: 'packages/live-host'
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
case "$(uname -m)" in
|
|
arm64) builder_arch='--arm64' ;;
|
|
x86_64) builder_arch='--x64' ;;
|
|
*) echo "::error::Unsupported runner architecture: $(uname -m)"; exit 1 ;;
|
|
esac
|
|
|
|
CSC_IDENTITY_AUTO_DISCOVERY=false npx electron-builder --config electron-builder.yml --mac dir "$builder_arch"
|
|
|
|
app="$(find release -type d -name 'Qwen Live Host.app' -print -quit)"
|
|
if [ -z "$app" ]; then
|
|
echo '::error::Packaged Qwen Live Host.app was not found.'
|
|
exit 1
|
|
fi
|
|
|
|
test -x "$app/Contents/MacOS/Qwen Live Host"
|
|
test -f "$app/Contents/Resources/app.asar"
|
|
|
|
asar_entries="$(npx --no-install asar list "$app/Contents/Resources/app.asar")"
|
|
if printf '%s\n' "$asar_entries" | grep -Eiq '(^|/)(native/|.*command[-_]?monitor|.*commandtap)'; then
|
|
echo '::error::Packaged app.asar contains a removed keyboard monitor or native helper.'
|
|
exit 1
|
|
fi
|
|
native_files="$(find "$app/Contents/Resources/native" -type f -print 2>/dev/null || true)"
|
|
expected_native="$app/Contents/Resources/native/qwen-live-appshot.node"
|
|
if [ "$native_files" != "$expected_native" ] || find "$app/Contents/Resources" -type f -print | grep -Eiq 'command[-_]?monitor|commandtap'; then
|
|
echo '::error::Packaged resources do not contain exactly the built-in Appshot module.'
|
|
exit 1
|
|
fi
|
|
|
|
extracted_asar="$(mktemp -d)"
|
|
packaged_entitlements="$(mktemp)"
|
|
trap 'rm -rf "$extracted_asar"; rm -f "$packaged_entitlements"' EXIT
|
|
npx --no-install asar extract "$app/Contents/Resources/app.asar" "$extracted_asar"
|
|
test -d "$extracted_asar/dist"
|
|
if grep -RIEq 'openWebShellWindow|web-shell-security|host\.open_session|inputMonitoring|installUrl|loadURL\(|@modelcontextprotocol|node:child_process' "$extracted_asar/dist"; then
|
|
echo '::error::Packaged app contains a forbidden external backend, process launcher, WebShell window, or Input Monitoring path.'
|
|
exit 1
|
|
fi
|
|
|
|
info_plist="$app/Contents/Info.plist"
|
|
if /usr/libexec/PlistBuddy -c 'Print :NSInputMonitoringUsageDescription' "$info_plist" >/dev/null 2>&1; then
|
|
echo '::error::Packaged app declares an Input Monitoring usage description.'
|
|
exit 1
|
|
fi
|
|
for unused_permission in NSBluetoothAlwaysUsageDescription NSBluetoothPeripheralUsageDescription; do
|
|
if /usr/libexec/PlistBuddy -c "Print :$unused_permission" "$info_plist" >/dev/null 2>&1; then
|
|
echo "::error::Packaged app declares unused permission $unused_permission."
|
|
exit 1
|
|
fi
|
|
done
|
|
/usr/libexec/PlistBuddy -c 'Print :NSMicrophoneUsageDescription' "$info_plist" >/dev/null
|
|
camera_usage="$(/usr/libexec/PlistBuddy -c 'Print :NSCameraUsageDescription' "$info_plist")"
|
|
if [ -z "${camera_usage//[[:space:]]/}" ]; then
|
|
echo '::error::Packaged app must describe its camera use.'
|
|
exit 1
|
|
fi
|
|
|
|
assert_live_host_entitlements() {
|
|
local entitlements_file="$1"
|
|
/usr/bin/plutil -convert json -o - "$entitlements_file" | node -e '
|
|
const chunks = [];
|
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
process.stdin.on("end", () => {
|
|
const value = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
const expected = [
|
|
"com.apple.security.cs.allow-jit",
|
|
"com.apple.security.cs.allow-unsigned-executable-memory",
|
|
"com.apple.security.device.audio-input",
|
|
"com.apple.security.device.camera",
|
|
];
|
|
const actual = Object.keys(value).sort();
|
|
if (
|
|
actual.length !== expected.length ||
|
|
actual.some((key, index) => key !== expected[index]) ||
|
|
expected.some((key) => value[key] !== true)
|
|
) {
|
|
console.error(
|
|
"Unexpected Live Host entitlements: " + JSON.stringify(value),
|
|
);
|
|
process.exit(1);
|
|
}
|
|
});
|
|
'
|
|
}
|
|
|
|
assert_live_host_entitlements build/entitlements.mac.plist
|
|
/usr/bin/codesign --force --deep --sign - --entitlements build/entitlements.mac.plist "$app"
|
|
/usr/bin/codesign --verify --deep --strict "$app"
|
|
/usr/bin/codesign -d --entitlements :- "$app" >"$packaged_entitlements" 2>/dev/null
|
|
assert_live_host_entitlements "$packaged_entitlements"
|