Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion .github/workflows/publish-studio-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ on:
workflow_dispatch:
inputs:
changelog:
description: User-facing Studio release summary
description: User-facing Studio updates, separated by semicolons
required: true
type: string
thin_bundles:
Expand Down Expand Up @@ -405,3 +405,60 @@ jobs:
raise SystemExit(1)
print(json.dumps(terminal["result"], ensure_ascii=False, indent=2))
PY

notify:
name: Notify Studio release subscribers
needs: [release-context, publish]
if: >-
github.event_name == 'workflow_dispatch' &&
github.repository == 'volcengine/veadk-python' &&
github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 10
environment: studio-release
steps:
- name: Send release card to Feishu groups
env:
WEBHOOK_URL: ${{ secrets.STUDIO_RELEASE_WEBHOOK_URL }}
WEBHOOK_KEY: ${{ secrets.STUDIO_RELEASE_WEBHOOK_KEY }}
RELEASE_VERSION: ${{ needs.release-context.outputs.version }}
RELEASE_CHANGELOG: ${{ inputs.changelog }}
run: |
python3 - <<'PYTHON'
import json
import os
import time
import urllib.error
import urllib.request

url = os.environ["WEBHOOK_URL"]
key = os.environ["WEBHOOK_KEY"]
if not url.startswith("https://") or len(key) < 32:
raise SystemExit("Studio release Webhook secrets are missing or invalid")
version = os.environ["RELEASE_VERSION"]
payload = json.dumps({
"version": version,
"date": f"{version[:4]}.{version[4:6]}.{version[6:8]}",
"changelog": os.environ["RELEASE_CHANGELOG"],
}).encode()
for attempt in range(3):
request = urllib.request.Request(url, data=payload, method="POST", headers={
"Content-Type": "application/json", "X-API-Key": key,
})
try:
with urllib.request.urlopen(request, timeout=190) as response:
result = json.load(response)
print(json.dumps(result, ensure_ascii=False))
if result.get("ok") is not True:
raise SystemExit("Release notification was not delivered to every group")
break
except urllib.error.HTTPError as error:
print(f"Notification HTTP status: {error.code}")
if error.code < 500 and error.code != 429:
raise SystemExit("Check Webhook configuration and bot group membership") from None
except (OSError, urllib.error.URLError):
print("Notification request interrupted")
if attempt == 2:
raise SystemExit("Release succeeded, but notification failed after three attempts")
time.sleep(5 * (attempt + 1))
PYTHON
7 changes: 7 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
A React web UI for VeADK / Google ADK agents. It talks to the standard ADK API
server that `veadk frontend` launches — no separate backend.

## Release notifications

The release workflow sends one Feishu card after both cloud providers finish
publishing. A separate VeFaaS Webhook discovers the app bot’s group memberships
and persists delivery results to avoid duplicate notifications on retries.
See [deployment and operation](service/studio_release_notifier/README.md).

## Features

- **Sandbox updates** in System Information compare each Tool's current image
Expand Down
69 changes: 69 additions & 0 deletions frontend/service/studio_release_notifier/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Studio release notifications

An authenticated VeFaaS HTTPS endpoint broadcasts the approved release card to
all groups joined by the Feishu application bot. No group IDs are configured in
GitHub. Add the bot to a group to subscribe it to future releases.

The `notify` job runs only after **both** Volcengine and BytePlus publishing jobs
succeed. It is separate from publication, so a notification failure does not
roll back a published version. The card contains the version, date and one list
of updates, with no environment label. Chinese and English semicolons and
newlines separate updates; empty entries are ignored. Markup in input is escaped.

## Deployment

Run from the repository root with the development dependencies installed:

```sh
python -m frontend.service.studio_release_notifier.deploy
```

Provide `VOLCENGINE_ACCESS_KEY`, `VOLCENGINE_SECRET_KEY` and, for temporary
credentials, `VOLCENGINE_SESSION_TOKEN` through the local environment.
The runtime needs `FEISHU_APP_ID`, `FEISHU_APP_SECRET`,
`STUDIO_RELEASE_WEBHOOK_KEY` (at least 32 characters) and
`NOTIFIER_PREVIEW_USER_ID` (the operator's open ID for this application).
Never commit these values.

The deployment creates or updates `veadk-studio-release-notifier` in cn-beijing,
with description `勿删:Studio 发版飞书群通知 Webhook`. It creates a dedicated
IAM role restricted to notification records under the existing `veadk-studio`
TOS bucket. Runtime cloud credentials come from VeFaaS IAM, not deployment AK/SK.
The function uses 1 vCPU and 2 GiB memory and a separate HTTPS gateway service.

Configure these GitHub repository or `studio-release` environment secrets:

| Name | Value |
| --- | --- |
| `STUDIO_RELEASE_WEBHOOK_URL` | The deployed HTTPS endpoint followed by `/release` |
| `STUDIO_RELEASE_WEBHOOK_KEY` | The same key configured in the function |

The Feishu app needs bot messaging and joined-group read permissions. The bot
must be available to the intended users and added to each target group.

## Endpoint contract

All endpoints require the `X-API-Key` header:

- `GET /readyz`: checks IAM credentials and access to notification storage
- `GET /groups`: lists groups joined by the bot
- `POST /preview`: sends a sample only to the configured operator
- `POST /release`: broadcasts a real release

POST bodies contain `version`, `date` (`YYYY.MM.DD`) and `changelog` (a string
or array of strings). Preview cards explicitly identify sample content.
Empty or oversized changelogs are rejected. With no groups, `/release` returns
409 and does not mark the release delivered.

A release's first request snapshots its recipient groups and content. Retries
must preserve that version, date and content; conflicting content returns 409.
New groups receive future releases, not historical retry notifications.
Each successful group's message ID is persisted in TOS. Transient failures
return 502, allowing the pipeline to retry only unfinished deliveries.
Concurrent retries use the same Feishu UUID for each version/group pair.

Feishu's UUID deduplication lasts one hour. If a delivery is still ambiguous
near that limit, its status becomes `needs_review` and automatic retries stop
for that group to avoid duplicates. Check whether the group received the message
before repairing the corresponding TOS record. Do not delete delivery records
or change the version just to retry a failed request.
13 changes: 13 additions & 0 deletions frontend/service/studio_release_notifier/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Loading
Loading