chore(release): 公开发布适配——泄漏净化 + README 修订 + 发布脚本与排除清单

This commit is contained in:
JOJO 2026-08-22 12:21:10 +08:00
parent 9602875391
commit 0f9fd1b25c
6 changed files with 224 additions and 35 deletions

View File

@ -13,7 +13,7 @@
- **构词**`Astr-`(希腊语 *astron*,星星/天体)+ `-ion`(粒子/状态后缀,如 photon、electron
- **意象**:星之子、星际粒子、来自宇宙尘埃的微小发光体。
- **寓意**Agent 像星际间传递信号的粒子,把人类意图传递到工具、文件、终端与子智能体之间;同时把零散任务像星尘聚合成恒星一样,聚合成可执行结果。
- **状态**:暂定名,后续若变更需同步更新 `AGENTS.md`、`claude.md` 与项目记忆 `astrion_naming`
- **状态**:暂定名,后续若变更需同步更新 `AGENTS.md`(及本地 `claude.md`,未随仓库发布)
- **中文参考**:阿斯特里恩;简称可考虑 **星子**
## 1) 当前项目结构(按代码现状)
@ -35,7 +35,7 @@
- `static/src/components/chat/monitor/MonitorDirector.ts`
- `static/src/stores/monitor.ts`
- `static/src/components/chat/monitor/*`
- **CLI 相关文档**
- **CLI 相关文档**(本地文档,未随仓库发布)
- `docs/cli_ui_display_spec.md`: CLI 显示层设计说明
- `docs/cli_slash_commands_spec.md`: CLI `/` 指令设计说明
- **其他子项目/资源**
@ -110,11 +110,6 @@
- 兼容启动方式:`python web_server.py`
- 统一入口:`python main.py`(当前实现会默认进入 Web 启动流程)
> **运行环境边界(重要)**:当前仓库(程序本体)跑在 **8091** 端口;**8092** 端口运行的是另一个本项目的代码副本所启动的智能体程序(即你自己的程序)。任何时候都禁止操作或修改 8092 端口的程序及其数据。两套环境的位置如下:
>
> - **macOS 开发环境**程序本体8091的数据存储在 `/Users/jojo/.astrion/astrion`你的程序8092的数据存储在 `/Users/jojo/.astrion/astrion-clone`
> - **Windows 环境**程序本体8091的数据存储在 `C:\Users\KOJO JOTARO\.astrion\astrion`你的程序8092代码位于 `E:\astrion-clone`,数据存储在 `C:\Users\KOJO JOTARO\.astrion\astrion-clone`
### Frontend根目录
- 安装依赖:`npm install`
- 构建:`npm run build --silent 2>&1 | tail -n 5`(默认只看最后 5 行,减少 Vite 构建资源列表等无关输出)
@ -199,7 +194,7 @@
7. **带文字容器固定高度**:所有含内部文字的选项 / 按钮 / 标签 / 容器固定高度,禁止因文字多少被撑大撑高;过长文字用省略号或内部滚动处理。
8. **窗口设最大尺寸 + 内部滚动**:所有弹窗 / 面板 / 列表设置 `max-height` / `max-width`,超出由内部容器滚动,不顶大整个窗口;滚动条要么隐藏,要么做样式适配,不暴露原生粗滚动条。
9. **实体面板禁止半透明**:实体 UI 容器(对话区 / 侧栏 / 下拉/二级菜单 / 抽屉 / 对话框本体 / 状态条 / tooltip / 输入栏)背景必须不透明,且不加 `backdrop-filter` 磨砂。唯一例外:遮罩层 scrim`--overlay-scrim``position:fixed;inset:0`)和刻意玻璃质感装饰保留半透明。
10. **主题变体必须与基础定义同文件(禁止跨文件补丁覆盖)**:组件的 dark/light/classic 主题变体写在该组件自己的样式文件里(文件内统一的 `body[data-theme='…'] { … }` 块),禁止把某组件的主题样式写到别的文件去覆盖。新增主题样式前先确认该组件基础定义所在文件;发现存量跨文件覆盖应顺手回迁并删除原覆盖。`!important` 只准用于遮盖第三方库样式,禁止用于压过项目自有定义(出现这种需求说明应该合并定义,而不是加 `!important`)。审计脚本:`python3 cache/style_audit.py` + `python3 cache/theme_diff.py`
10. **主题变体必须与基础定义同文件(禁止跨文件补丁覆盖)**:组件的 dark/light/classic 主题变体写在该组件自己的样式文件里(文件内统一的 `body[data-theme='…'] { … }` 块),禁止把某组件的主题样式写到别的文件去覆盖。新增主题样式前先确认该组件基础定义所在文件;发现存量跨文件覆盖应顺手回迁并删除原覆盖。`!important` 只准用于遮盖第三方库样式,禁止用于压过项目自有定义(出现这种需求说明应该合并定义,而不是加 `!important`)。审计脚本:`python3 cache/style_audit.py` + `python3 cache/theme_diff.py`(本地脚本,未随仓库发布)
11. **大面积色块禁止纯黑/近黑**:任何窗口的 hover、头部、背景等大面积色块禁止使用纯黑`#000`)或肉眼近黑(如 `#0a0a0a`/`#0f0f0f` 一档的颜色深色模式同样如此。深色下的层次靠中性灰阶拉开hover 用 `--hover-bg`(深色为白色微量 tint提亮而非压黑弹窗/面板头部优先用分隔线区分而非深色衬底(参考 `VersioningDialog.vue` 头部)。纯黑/近黑只允许用于小范围强调渲染(如行内代码衬底等小面积场景)。
## 5.6) 工具结果显示规范(强制)
@ -290,14 +285,14 @@ AI 执行以下流程时,每一步都要向用户说明在做什么:
## 8) Android App 发布联动要求(重要)
当修改前端并需要发布 Android WebView App 时,必须同步执行以下步骤(依据 `doc/android_app_release_and_update.md`
当修改前端并需要发布 Android WebView App 时,必须同步执行以下步骤(依据 `doc/android_app_release_and_update.md`,本地文档未随仓库发布
1. 同时更新版本信息
- `android-webview-app/app/build.gradle.kts`:递增 `versionCode`,更新 `versionName`
2. 同时更新更新说明
- `android-webview-app/APP_CHANGELOG.md`:在顶部新增当前版本说明
3. 完成修改后提醒用户运行上传脚本
- 在发布流程中运行:`bash /Users/jojo/Desktop/agents/正在修复中/agents/upload_android_apk.sh`
- 在发布流程中运行:`bash ./upload_android_apk.sh`(本地私有脚本,未随仓库发布)
> 禁止只改前端代码而不更新版本号/更新说明,否则会导致客户端更新提示与分发信息不一致。
@ -313,7 +308,7 @@ AI 执行以下流程时,每一步都要向用户说明在做什么:
## 10) 宿主机权限模式与沙箱执行机制2026-05 更新)
完整说明文档:`docs/host_sandbox_and_permission_model.md`
完整说明文档:`docs/host_sandbox_and_permission_model.md`(本地文档,未随仓库发布)
为避免误解,当前系统有两套独立但叠加的控制:
@ -393,7 +388,7 @@ AI 执行以下流程时,每一步都要向用户说明在做什么:
## 11) 多智能体对话类型multi-agent conversation type
> **2026-08 重构**:已废除「多智能体模式」全局状态,多智能体是**对话的不可变属性**`metadata.multi_agent_mode = true`创建时确定、不可变。URL 统一为 `/<conv_id>`(旧 `/multiagent/*` 由前端 bootstrap 重定向到裸路径)。数据目录 `~/.astrion/astrion/host/mutiagents/`(保留原拼写)。完整设计与踩坑记录见项目记忆 `multi_agent_mode_design`重构方案见 `docs/conversation_type_unification_plan.md`,本节只列 Agent 改代码时必须知道的硬约束。
> **2026-08 重构**:已废除「多智能体模式」全局状态,多智能体是**对话的不可变属性**`metadata.multi_agent_mode = true`创建时确定、不可变。URL 统一为 `/<conv_id>`(旧 `/multiagent/*` 由前端 bootstrap 重定向到裸路径)。数据目录 `~/.astrion/astrion/host/mutiagents/`(保留原拼写)。重构方案见 `docs/conversation_type_unification_plan.md`(本地设计文档,未随仓库发布),本节只列 Agent 改代码时必须知道的硬约束。
### 11.0 前端对话类型模型(重构后)
@ -429,7 +424,7 @@ AI 执行以下流程时,每一步都要向用户说明在做什么:
- 输出端(`SubAgentTask._forward_output_to_master`):子智能体每次 assistant 文本输出都封为标准格式消息并 `push_master_message` 到会话的 `MultiAgentState.pending_master_messages`,不再做事。
- 接收端(`MultiAgentState` + dispatch根据主智能体当前状态选择插入方式
- **情况1主运行中**:在 `execute_tool_calls` 末尾 `process_multi_agent_master_messages(inline=True, after_tool_call_id=...)` 插入到下一轮模型 messages 列表里(详见 `server/chat_flow_tool_loop.py:1326`)。
- **情况2主空闲**`poll_multi_agent_notifications` spawn 出后台 poll主对话空闲且 pool 有消息就 drain`_dispatch_multi_agent_idle_messages` 创建 `task_type="notice"` 的后续 task触发新一轮工作。完整顺序见项目记忆 multi_agent_mode_design 中的「情况2完整链路」图。
- **情况2主空闲**`poll_multi_agent_notifications` spawn 出后台 poll主对话空闲且 pool 有消息就 drain`_dispatch_multi_agent_idle_messages` 创建 `task_type="notice"` 的后续 task触发新一轮工作。
- **情况3主最后一轮无工具调用**:主循环 `if not tool_calls:` 分支调 `process_multi_agent_master_messages(inline=False)``continue` 继续迭代Team Leader 在同 task 续跑处理子智能体输出。实现上与情况2同路径即情况3通过后续 task 完成情况2
**情况2 硬约束(调试踩过坑)**
@ -517,13 +512,13 @@ AI 执行以下流程时,每一步都要向用户说明在做什么:
---
注:本节按「现有架构 + 多智能体分支」方案描述,与项目记忆 `multi_agent_mode_design` 同步;如两者冲突以代码为准并同步修订本节。
注:本节按「现有架构 + 多智能体分支」方案描述;如与代码冲突,以代码为准并同步修订本节。
---
## 12) 对话级主任务门闸与单写者不变量2026-08-12
> 事故背景一个对话并发运行了两个主聊天任务socketio 用户任务 + 完成通知轮询器派发的通知任务),交叉写入共享 `conversation_history`,产生 `assistant→assistant→tool→tool` 乱序段,最终 API 400 `tool_call_id is not found`、通知永久丢失。完整根因与修复细节见项目记忆 `main_task_gate_and_parallel_universe`
> 事故背景一个对话并发运行了两个主聊天任务socketio 用户任务 + 完成通知轮询器派发的通知任务),交叉写入共享 `conversation_history`,产生 `assistant→assistant→tool→tool` 乱序段,最终 API 400 `tool_call_id is not found`、通知永久丢失。本节机制即为修复该事故引入
### 12.1 单写者不变量(核心约束)

View File

@ -46,7 +46,7 @@
```bash
# 1. 克隆
git clone <仓库地址>
git clone https://github.com/JOJO6618/astrion.git
cd astrion
# 2. 初始化:创建 venv、安装依赖、运行交互式配置向导
@ -164,8 +164,8 @@ docker build -f docker/terminal.Dockerfile -t my-agent-shell:latest .
| `server.port` / `server.host` | `WEB_SERVER_PORT` / `WEB_SERVER_HOST` | Web 监听 |
| `admin.username` | `AGENT_ADMIN_USERNAME` | 管理员用户名 |
| `admin.password_hash` | `AGENT_ADMIN_PASSWORD_HASH` | 管理员密码哈希(见「管理员账户」) |
| `admin.secondary_password_hash` | `ADMIN_SECONDARY_PASSWORD_HASH` | 可选:敏感操作二级密码 |
| `secrets.web_secret_key` | `WEB_SECRET_KEY` | Session 签名密钥(随机 hex |
| `admin.secondary_password_hash` | `ADMIN_SECONDARY_PASSWORD_HASH` | 敏感操作二级密码**web 模式实际必需**:未配置时管理接口校验恒失败,管理面板功能不可用) |
| `secrets.web_secret_key` | `WEB_SECRET_KEY` | Session 签名密钥(随机 hex**web 模式强烈建议配置**:未配置时使用临时密钥,重启后所有登录会话失效 |
| `secrets.api_token_secret` | `API_TOKEN_SECRET` | API Token 加密密钥(随机 hex |
| `models.default_response_max_tokens` | `AGENT_DEFAULT_RESPONSE_MAX_TOKENS` | 单轮响应 token 全局上限 |
| `search.tavily_api_key(_2)` | `AGENT_TAVILY_API_KEY(_2)` | Tavily 搜索 Key可选 |
@ -214,9 +214,15 @@ docker build -f docker/terminal.Dockerfile -t my-agent-shell:latest .
python3 -c "from werkzeug.security import generate_password_hash; print(generate_password_hash('你的密码'))"
```
管理员登录邮箱自动生成,规则为 `<用户名>@local`(例如用户名为 `admin`,则登录邮箱为 `admin@local`)。
### 普通用户web 模式)
**邀请码注册制**`/register` 注册必须提供有效邀请码;邀请码由管理员在用户管理中生成(支持次数限制)。
**邀请码注册制**`/register` 注册必须提供有效邀请码;邀请码由管理员在管理面板(`/admin/monitor`)的「邀请码」标签页生成(支持次数限制)。
> ⚠️ **安全提示**
> - 注册邮箱仅用于登录与查重,**系统不发送验证邮件**——邀请码是注册的唯一实质门槛,请妥善保管。
> - 新用户的默认权限模式为「无限制」(`unrestricted`,可执行任意终端命令)。多用户或包含不可信用户的场景下,建议用户在个人空间将默认权限模式调整为 `approval` / `readonly`
## 项目结构

View File

@ -3,5 +3,4 @@ name: mcp-tool-config
description: 指导模型在宿主机模式下如何给自己配置新的mcp工具
---
Just Read This File
skills/mcp-tool-config/mcp-tool-config-guide.md
阅读本目录下的 [mcp-tool-config-guide.md](mcp-tool-config-guide.md),获取完整的 MCP 工具配置指南。

View File

@ -1,7 +1,6 @@
# 本项目新增 MCP 工具配置指南
> 最后更新2026-04-26
> 适用目录:`/Users/jojo/Desktop/agents/正在修复中/agents`
> 最后更新2026-08-22
本文面向后续维护者,说明如何在当前项目里新增并接入一个 MCP 工具MCP Server
@ -44,7 +43,7 @@
仓库已有可直接参考的测试服务:
- `/Users/jojo/Desktop/agents/正在修复中/agents/scripts/mcp_calculator_server.py`
- `scripts/mcp_calculator_server.py`
这个示例使用 stdio支持
@ -146,11 +145,11 @@
- `MCP_PROTOCOL_VERSION=2025-06-18`
- `MCP_DEFAULT_TIMEOUT_SECONDS=25`
如果你只是个AI那就去改这个文件)
AI 维护者请直接改这个文件)
默认配置文件是运行态文件:
默认配置文件是运行态文件`DATA_DIR` 解析见 `config/paths.py`
- `/Users/jojo/Desktop/agents/正在修复中/agents/data/mcp_servers.json`
- `~/.astrion/astrion/<mode>/data/mcp_servers.json`
---
@ -191,12 +190,12 @@
## 10. 关键代码位置(便于二次开发)
- MCP 配置:`/Users/jojo/Desktop/agents/正在修复中/agents/config/mcp.py`
- 服务注册表:`/Users/jojo/Desktop/agents/正在修复中/agents/modules/mcp_server_registry.py`
- MCP 客户端桥接:`/Users/jojo/Desktop/agents/正在修复中/agents/modules/mcp_client_manager.py`
- 管理 API`/Users/jojo/Desktop/agents/正在修复中/agents/server/admin.py`
- MCP 配置:`config/mcp.py`
- 服务注册表:`modules/mcp_server_registry.py`
- MCP 客户端桥接:`modules/mcp_client_manager/`(子包)
- 管理 API`server/admin.py`
- 工具定义/执行:
- `/Users/jojo/Desktop/agents/正在修复中/agents/core/main_terminal_parts/tools_definition.py`
- `/Users/jojo/Desktop/agents/正在修复中/agents/core/main_terminal_parts/tools_execution.py`
- 管理端页面:`/Users/jojo/Desktop/agents/正在修复中/agents/static/src/admin/PolicyApp.vue`
- 测试示例服务:`/Users/jojo/Desktop/agents/正在修复中/agents/scripts/mcp_calculator_server.py`
- `core/main_terminal_parts/tools_definition/`(子包,`tools_definition.py` 为兼容入口)
- `core/main_terminal_parts/tools_execution.py`
- 管理端页面:`static/src/admin/PolicyApp.vue`
- 测试示例服务:`scripts/mcp_calculator_server.py`

View File

@ -0,0 +1,11 @@
# 公开发布排除清单GitHub public 分支)
# 用途:发布脚本同步 main → public 时,按本清单剔除文件。
# 语义:清单内文件在私人仓库 main 正常跟踪、正常工作;仅不进入 GitHub 公开仓库。
# 格式rsync --exclude-from 兼容模式(每行一条,# 开头为注释)。
# 维护规则:新增敏感/内部文件时追加一行;净化后可公开发布的条目应移除。
# ---- 内部调研笔记(已完成使命,公开后易过时误导)----
workflow_research/
# ---- docs/ 内部文档(分析/方案过程稿AGENTS.md 中对 docs/ 的引用需随 AGENTS.md 处理一并调整)----
docs/

179
scripts/release_public.sh Executable file
View File

@ -0,0 +1,179 @@
#!/usr/bin/env bash
# =============================================================================
# 公开发布脚本main → GitHub 公开仓库
#
# 机制:
# 1. 本地 public 分支为 orphan 分支(与 main 无共同历史),防止 main 历史中
# 曾出现过的敏感文件(如旧版 AGENTS.md随历史泄漏到公开仓库。
# 2. 内容来源是 `git archive main`(仅含被跟踪文件),未跟踪文件
# .env / node_modules / .astrion/ 等)天然不会混入。
# 3. 按 scripts/public_release_excludes.txt 剔除「被跟踪但不公开」的条目。
# 4. 发布前执行泄漏扫描(硬闸),命中即中止。
#
# 用法:
# bash scripts/release_public.sh # 交互式(摘要后逐确认 commit / push
# bash scripts/release_public.sh --dry-run # 只导出+扫描+展示摘要,不提交不推送
# bash scripts/release_public.sh --yes # 跳过确认(仅限已获得明确授权时)
# =============================================================================
set -euo pipefail
# ---- 配置 -------------------------------------------------------------------
PUBLIC_BRANCH="public" # 本地公开分支orphan
GITHUB_REMOTE="github" # GitHub remote 名
GITHUB_TARGET_BRANCH="main" # 推送到 GitHub 的目标分支
EXCLUDES_FILE="scripts/public_release_excludes.txt"
# 泄漏扫描模式(上下文特征,非裸词;避免误报公开信息如 GitHub 用户名)
LEAK_PATTERNS=(
'/Users/jojo'
'KOJO JOTARO'
'C:\\Users\\KOJO'
'cyjai\.com'
'git\.cyjai'
'正在修复中'
'astrion-clone'
'E:\\astrion'
)
# ---- 参数 -------------------------------------------------------------------
DRY_RUN=false
ASSUME_YES=false
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=true ;;
--yes) ASSUME_YES=true ;;
-h|--help)
grep '^#' "$0" | sed 's/^# \{0,1\}//' | head -20
exit 0 ;;
*) echo "未知参数: $arg"; exit 2 ;;
esac
done
confirm() { # $1=提示语
if $ASSUME_YES; then return 0; fi
local reply
read -r -p "$1 [y/N] " reply
[[ "$reply" == "y" || "$reply" == "Y" ]]
}
# ---- 前置检查 ---------------------------------------------------------------
cd "$(dirname "$0")/.."
echo "==> 仓库根: $(pwd)"
if ! git remote get-url "$GITHUB_REMOTE" >/dev/null 2>&1; then
echo "!! 未找到 remote '$GITHUB_REMOTE'。请先执行:"
echo " git remote add $GITHUB_REMOTE <GitHub 仓库地址>"
exit 1
fi
CURRENT_BRANCH=$(git symbolic-ref --short HEAD)
if [[ "$CURRENT_BRANCH" != "main" ]]; then
echo "!! 当前分支是 $CURRENT_BRANCH,请先切换到 main。"
exit 1
fi
MAIN_SHA=$(git rev-parse --short main)
echo "==> 发布来源: main @ ${MAIN_SHA}(未提交到 main 的改动不会包含)"
# 工作区脏检测:未提交改动不会进入发布内容,明确警告防止「改了忘提交」
if [[ -n "$(git status --porcelain)" ]]; then
echo "!! 警告:工作区存在未提交改动,以下内容【不会】出现在本次发布中:"
git status --short | head -20
if ! $DRY_RUN; then
confirm "仍要基于 main 最新提交继续吗?" || { echo "已取消。请先提交或暂存改动。"; exit 1; }
fi
fi
# ---- 准备 public orphan 分支 ------------------------------------------------
if ! git show-ref --verify --quiet "refs/heads/$PUBLIC_BRANCH"; then
echo "==> 首次发布:创建 orphan 分支 '$PUBLIC_BRANCH'(空初始提交,与 main 无历史关联)"
EMPTY_TREE=$(git mktree </dev/null)
INIT_COMMIT=$(git commit-tree "$EMPTY_TREE" -m "chore: initialize public release branch")
git update-ref "refs/heads/$PUBLIC_BRANCH" "$INIT_COMMIT"
fi
# ---- 准备临时 worktree不碰当前工作区 -------------------------------------
WT=$(mktemp -d /tmp/astrion_public_release.XXXXXX)
cleanup() {
git worktree remove --force "$WT" >/dev/null 2>&1 || true
git worktree prune >/dev/null 2>&1 || true
}
trap cleanup EXIT
git worktree add --force "$WT" "$PUBLIC_BRANCH" >/dev/null
echo "==> 临时 worktree: $WT"
# 清空 worktree 内容(保留 .git保证被删除的文件能反映为删除
find "$WT" -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} +
# ---- 导出 main 内容 ----------------------------------------------------------
git archive main | tar -x -C "$WT"
echo "==> 已从 main 导出被跟踪文件"
# ---- 按排除清单剔除 ----------------------------------------------------------
if [[ -f "$EXCLUDES_FILE" ]]; then
while IFS= read -r pattern; do
[[ -z "$pattern" || "$pattern" =~ ^[[:space:]]*# ]] && continue
target="$WT/${pattern%/}"
if [[ -e "$target" ]]; then
rm -rf "$target"
echo "==> 已剔除: $pattern"
fi
done < "$EXCLUDES_FILE"
else
echo "!! 警告:排除清单 $EXCLUDES_FILE 不存在"
fi
# ---- 泄漏扫描(硬闸) --------------------------------------------------------
echo "==> 执行泄漏扫描..."
LEAK_HIT=false
for pattern in "${LEAK_PATTERNS[@]}"; do
hits=$(grep -rInE --exclude-dir=.git --exclude=.git --exclude=release_public.sh -- "$pattern" "$WT" 2>/dev/null || true)
if [[ -n "$hits" ]]; then
LEAK_HIT=true
echo "!! 命中模式 [$pattern]:"
echo "$hits" | sed "s|$WT/||" | head -10
fi
done
if $LEAK_HIT; then
echo "!! 泄漏扫描未通过,已中止。请净化后重试,或确认误报后调整扫描模式。"
exit 1
fi
echo "==> 泄漏扫描通过"
# ---- 摘要 --------------------------------------------------------------------
cd "$WT"
git add -A
echo
echo "==================== 变更摘要(相对上一次公开提交) ===================="
git status --short | head -60
CHANGED=$(git status --short | wc -l | tr -d ' ')
TOTAL=$(find . -type f ! -path './.git/*' | wc -l | tr -d ' ')
echo "--------------------------------------------------------------------"
echo "变更条目: $CHANGED 公开文件总数: $TOTAL"
echo "======================================================================"
echo
if $DRY_RUN; then
echo "==> dry-run 结束(未提交、未推送)。临时 worktree 已自动清理。"
exit 0
fi
if [[ "$CHANGED" == "0" ]]; then
echo "==> 与上一次公开提交相比无变化。"
confirm "仍然创建一个空发布提交并推送吗?" || { echo "已取消。"; exit 0; }
fi
# ---- 提交 --------------------------------------------------------------------
DATE=$(date +%Y-%m-%d)
echo "==> 提交信息: release: sync from main @$MAIN_SHA ($DATE)"
confirm "确认在 '$PUBLIC_BRANCH' 分支上创建发布提交?" || { echo "已取消。"; exit 0; }
git commit -m "release: sync from main @$MAIN_SHA ($DATE)"
# ---- 推送 --------------------------------------------------------------------
echo "==> 推送目标: $GITHUB_REMOTE $PUBLIC_BRANCH:$GITHUB_TARGET_BRANCH"
confirm "确认推送到 GitHub公开仓库" || { echo "已取消推送(本地提交已保留)。 "; exit 0; }
git push "$GITHUB_REMOTE" "$PUBLIC_BRANCH:$GITHUB_TARGET_BRANCH"
echo "==> 完成。公开仓库已更新到 main @$MAIN_SHA 的快照。"