feat(memory): P1 长期记忆升级 —— 异步攒批 Consolidate + 软删 + importance/last_seen #1
@@ -0,0 +1,60 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
# 打 vX.Y.Z 标签即触发:各平台用 wails 构建桌面端安装包 → 发布到 GitHub Release。
|
||||||
|
# 用户旧版 App 启动时查 /releases/latest,发现新版即提示下载(见 frontend/src/lib/version.ts)。
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ["v*"]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write # 创建 release + 上传产物需要
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: 构建 ${{ matrix.label }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: macos-latest
|
||||||
|
label: macOS (universal)
|
||||||
|
platform: darwin/universal
|
||||||
|
- os: windows-latest
|
||||||
|
label: Windows (amd64)
|
||||||
|
platform: windows/amd64
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "1.25"
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
- name: 安装 wails CLI
|
||||||
|
run: go install github.com/wailsapp/wails/v2/cmd/wails@v2.12.0
|
||||||
|
|
||||||
|
- name: 构建桌面端(wails build;GOWORK=off 因 desktop 不在工作区)
|
||||||
|
working-directory: sundynix-desktop
|
||||||
|
env:
|
||||||
|
GOWORK: off
|
||||||
|
run: wails build -clean -platform ${{ matrix.platform }}
|
||||||
|
|
||||||
|
# macOS:.app 是目录,打包成 zip 才能作为单文件资产分发。
|
||||||
|
- name: 打包 macOS 产物
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
working-directory: sundynix-desktop/build/bin
|
||||||
|
run: zip -r -y sundynix_desktop_macos_universal.zip sundynix_desktop.app
|
||||||
|
|
||||||
|
- name: 重命名 Windows 产物
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
working-directory: sundynix-desktop/build/bin
|
||||||
|
run: ren sundynix_desktop.exe sundynix_desktop_windows_amd64.exe
|
||||||
|
|
||||||
|
- name: 发布到 GitHub Release(按 tag 创建/追加资产)
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
fail_on_unmatched_files: false
|
||||||
|
files: |
|
||||||
|
sundynix-desktop/build/bin/sundynix_desktop_macos_universal.zip
|
||||||
|
sundynix-desktop/build/bin/sundynix_desktop_windows_amd64.exe
|
||||||
@@ -12,6 +12,7 @@ import { RunsView } from "./views/RunsView";
|
|||||||
import { Home } from "./views/Home";
|
import { Home } from "./views/Home";
|
||||||
import { Placeholder } from "./views/Placeholder";
|
import { Placeholder } from "./views/Placeholder";
|
||||||
import { CommandPalette, type Command } from "./components/CommandPalette";
|
import { CommandPalette, type Command } from "./components/CommandPalette";
|
||||||
|
import { UpdateBanner } from "./components/UpdateBanner";
|
||||||
import { Login } from "./views/Login";
|
import { Login } from "./views/Login";
|
||||||
import { submitTask, streamTokens, streamExec, authMe, logout, type Identity, type AuthUser } from "./lib/api";
|
import { submitTask, streamTokens, streamExec, authMe, logout, type Identity, type AuthUser } from "./lib/api";
|
||||||
import type { TaskDsl } from "./lib/dsl";
|
import type { TaskDsl } from "./lib/dsl";
|
||||||
@@ -158,6 +159,7 @@ export default function App() {
|
|||||||
className="pointer-events-none absolute inset-x-0 top-0 h-64 opacity-60"
|
className="pointer-events-none absolute inset-x-0 top-0 h-64 opacity-60"
|
||||||
style={{ background: "radial-gradient(60% 100% at 50% 0%, rgba(124,92,246,0.10), transparent 70%)" }}
|
style={{ background: "radial-gradient(60% 100% at 50% 0%, rgba(124,92,246,0.10), transparent 70%)" }}
|
||||||
/>
|
/>
|
||||||
|
<UpdateBanner />
|
||||||
<TopBar user={user} onLogout={onLogout} onCommand={() => setCmdOpen(true)} />
|
<TopBar user={user} onLogout={onLogout} onCommand={() => setCmdOpen(true)} />
|
||||||
<div className="relative flex min-h-0 flex-1">
|
<div className="relative flex min-h-0 flex-1">
|
||||||
<LeftNav active={view} onSelect={setView} />
|
<LeftNav active={view} onSelect={setView} />
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Download, X } from "lucide-react";
|
||||||
|
import { checkUpdate, openExternal, type ReleaseInfo } from "../lib/version";
|
||||||
|
|
||||||
|
// 启动时查 GitHub Releases,有新版则在顶部显示一条可关闭的横幅,点击去下载页。
|
||||||
|
export function UpdateBanner() {
|
||||||
|
const [rel, setRel] = useState<ReleaseInfo | null>(null);
|
||||||
|
const [dismissed, setDismissed] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let alive = true;
|
||||||
|
void checkUpdate().then((r) => {
|
||||||
|
if (alive) setRel(r);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
alive = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!rel || dismissed) return null;
|
||||||
|
return (
|
||||||
|
<div className="flex shrink-0 items-center gap-3 border-b border-brand/30 bg-brand/10 px-4 py-1.5 text-[12px] text-brand-200">
|
||||||
|
<Download className="h-3.5 w-3.5 text-brand-400" />
|
||||||
|
<span>
|
||||||
|
新版本 <span className="font-semibold">v{rel.version}</span> 可用
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => openExternal(rel.url)}
|
||||||
|
className="rounded bg-brand/20 px-2 py-0.5 text-brand-200 hover:bg-brand/30"
|
||||||
|
>
|
||||||
|
前往下载
|
||||||
|
</button>
|
||||||
|
<button onClick={() => setDismissed(true)} className="ml-auto text-slate-500 hover:text-slate-300" title="忽略">
|
||||||
|
<X className="h-3.5 w-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// 桌面端版本与"检查更新"——查 GitHub Releases 最新版,比当前版本新则提示下载。
|
||||||
|
// 发版流程:bump 此处 APP_VERSION(= package.json version)→ 打 tag vX.Y.Z → release workflow 自动构建+发布。
|
||||||
|
|
||||||
|
export const APP_VERSION = "0.1.0"; // 当前桌面端版本(与 git tag 去掉 v 前缀对齐)
|
||||||
|
|
||||||
|
const REPO = "blizzardzhang/sundynix-agentix";
|
||||||
|
|
||||||
|
export interface ReleaseInfo {
|
||||||
|
version: string; // 最新版本号(不含 v)
|
||||||
|
url: string; // release 页面(用户在此选 mac/win 安装包)
|
||||||
|
notes: string; // 更新日志
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkUpdate 查 GitHub 最新 release;有比当前更新的版本则返回信息,否则 null(含出错/限流时静默)。
|
||||||
|
export async function checkUpdate(): Promise<ReleaseInfo | null> {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`, {
|
||||||
|
headers: { Accept: "application/vnd.github+json" },
|
||||||
|
});
|
||||||
|
if (!res.ok) return null; // 404=还没发过 release / 403=限流 → 静默
|
||||||
|
const d = (await res.json()) as { tag_name?: string; html_url?: string; body?: string };
|
||||||
|
const latest = String(d.tag_name ?? "").replace(/^v/, "");
|
||||||
|
if (!latest || !isNewer(latest, APP_VERSION)) return null;
|
||||||
|
return { version: latest, url: d.html_url ?? `https://github.com/${REPO}/releases/latest`, notes: d.body ?? "" };
|
||||||
|
} catch {
|
||||||
|
return null; // 离线等 → 不打扰
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// openExternal 在系统浏览器打开链接(Wails 用 runtime.BrowserOpenURL,浏览器模式回退 window.open)。
|
||||||
|
export function openExternal(url: string): void {
|
||||||
|
const w = window as unknown as { runtime?: { BrowserOpenURL?: (u: string) => void } };
|
||||||
|
if (w.runtime?.BrowserOpenURL) {
|
||||||
|
w.runtime.BrowserOpenURL(url);
|
||||||
|
} else {
|
||||||
|
window.open(url, "_blank");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isNewer 语义化版本比较:a 是否比 b 新(按 major.minor.patch)。
|
||||||
|
function isNewer(a: string, b: string): boolean {
|
||||||
|
const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
|
||||||
|
const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const x = pa[i] ?? 0;
|
||||||
|
const y = pb[i] ?? 0;
|
||||||
|
if (x !== y) return x > y;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user