-
-
Notifications
You must be signed in to change notification settings - Fork 347
release v0.8.4 #1243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
release v0.8.4 #1243
Changes from all commits
ed95b48
5bb7ad5
c5d02b9
8ceb31d
549defa
214aeba
08599ec
09d549c
e3070dd
3ade94c
d4b8cee
492c508
58346ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -12,7 +12,7 @@ ENV NEXT_TELEMETRY_DISABLED=1 | |||||
| ENV CI=true | ||||||
| RUN --mount=type=cache,target=/app/.next/cache bun run build | ||||||
|
|
||||||
| FROM node:20-slim AS runner | ||||||
| FROM node:22-slim AS runner | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Prompt To Fix With AIThis is a comment left during a code review.
Path: Dockerfile
Line: 15
Comment:
`node:22-slim` is a rolling tag that can resolve to any 22.x patch release. The `zstdDecompressSync` API (used in `request-body-codec.ts`) was first shipped in Node 22.15.0. If this image is built against a stale Docker cache that points to an earlier 22.x build, the decompression call will fail at runtime with `TypeError: zstdDecompressSync is not a function`. Pinning to `node:22.15-slim` (or later) makes the minimum requirement explicit and matches what `package.json` `engines` already declares.
```suggestion
FROM node:22.15-slim AS runner
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: 是的,Node 官方 Docker 镜像提供 Citations:
建议将运行时基础镜像固定到精确补丁版本,避免使用漂移标签(Dockerfile 第15行:
🤖 Prompt for AI Agents
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Medium] [STANDARD-VIOLATION] Rolling Docker tag doesn't guarantee the minimum Node version required by
The Suggested fix: FROM node:22.15-slim AS runner |
||||||
| WORKDIR /app | ||||||
| ENV NODE_ENV=production | ||||||
| ENV PORT=3000 | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,9 @@ | |
| "name": "claude-code-hub", | ||
| "version": "0.8.0", | ||
| "private": true, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The PR title is "release v0.8.4" but Prompt To Fix With AIThis is a comment left during a code review.
Path: package.json
Line: 4
Comment:
**Version not bumped for this release**
The PR title is "release v0.8.4" but `package.json` still declares `"version": "0.8.0"`. Any build artifact, API response, or UI element that reads the package version will report 0.8.0, making it impossible to distinguish this release from prior ones by version string alone.
How can I resolve this? If you propose a fix, please make it concise. |
||
| "engines": { | ||
| "node": ">=22.15.0" | ||
| }, | ||
| "scripts": { | ||
| "dev": "tsgo -p tsconfig.json --noEmit && next dev --port 13500", | ||
| "dev:server": "NODE_ENV=development node server.js", | ||
|
|
@@ -89,6 +92,7 @@ | |
| "dotenv": "^17", | ||
| "drizzle-orm": "^0.45", | ||
| "fetch-socks": "^1", | ||
| "fflate": "^0.8.2", | ||
| "framer-motion": "^12", | ||
| "hono": "^4", | ||
| "html2canvas": "^1", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -178,6 +178,13 @@ export async function createErrorRuleAction(data: { | |
|
|
||
| // 刷新缓存(事件广播,支持多 worker 同步) | ||
| await emitErrorRulesUpdated(); | ||
| // 上面的 emit 已在本进程触发一次携带最新数据的 reload,这里复用它即可(无需补跑第二轮)。 | ||
| // reload 仅为本进程缓存同步(跨 worker 由 emit 覆盖),失败不应把已成功的写入误报为失败。 | ||
| try { | ||
| await errorRuleDetector.reload(); | ||
| } catch (reloadError) { | ||
| logger.warn("[ErrorRulesAction] Failed to reload detector after mutation", { reloadError }); | ||
| } | ||
|
Comment on lines
179
to
+187
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 把 现在只有 可选修正- await emitErrorRulesUpdated();
+ try {
+ await emitErrorRulesUpdated();
+ } catch (emitError) {
+ logger.warn("[ErrorRulesAction] Failed to broadcast error-rule update", { emitError });
+ }
try {
await errorRuleDetector.reload();
} catch (reloadError) {
logger.warn("[ErrorRulesAction] Failed to reload detector after mutation", { reloadError });
}Also applies to: 321-327, 382-388 🤖 Prompt for AI Agents |
||
|
|
||
| revalidatePath("/settings/error-rules"); | ||
|
|
||
|
|
@@ -311,6 +318,13 @@ export async function updateErrorRuleAction( | |
|
|
||
| // 刷新缓存(事件广播,支持多 worker 同步) | ||
| await emitErrorRulesUpdated(); | ||
| // 上面的 emit 已在本进程触发一次携带最新数据的 reload,这里复用它即可(无需补跑第二轮)。 | ||
| // reload 仅为本进程缓存同步(跨 worker 由 emit 覆盖),失败不应把已成功的写入误报为失败。 | ||
| try { | ||
| await errorRuleDetector.reload(); | ||
| } catch (reloadError) { | ||
| logger.warn("[ErrorRulesAction] Failed to reload detector after mutation", { reloadError }); | ||
| } | ||
|
|
||
| revalidatePath("/settings/error-rules"); | ||
|
|
||
|
|
@@ -365,6 +379,13 @@ export async function deleteErrorRuleAction(id: number): Promise<ActionResult> { | |
|
|
||
| // 刷新缓存(事件广播,支持多 worker 同步) | ||
| await emitErrorRulesUpdated(); | ||
| // 上面的 emit 已在本进程触发一次携带最新数据的 reload,这里复用它即可(无需补跑第二轮)。 | ||
| // reload 仅为本进程缓存同步(跨 worker 由 emit 覆盖),失败不应把已成功的写入误报为失败。 | ||
| try { | ||
| await errorRuleDetector.reload(); | ||
| } catch (reloadError) { | ||
| logger.warn("[ErrorRulesAction] Failed to reload detector after mutation", { reloadError }); | ||
| } | ||
|
|
||
| revalidatePath("/settings/error-rules"); | ||
|
|
||
|
|
@@ -412,8 +433,9 @@ export async function refreshCacheAction(): Promise< | |
| // 1. 同步默认规则到数据库 | ||
| const syncResult = await repo.syncDefaultErrorRules(); | ||
|
|
||
| // 2. 重新加载缓存 | ||
| await errorRuleDetector.reload(); | ||
| // 2. 重新加载缓存:手动刷新必须读到刚同步的默认规则, | ||
| // 若已有在途 reload 则排队补跑一轮(queueIfRunning),确保拿到同步后的最新快照。 | ||
| await errorRuleDetector.reload({ queueIfRunning: true }); | ||
|
|
||
| const stats = errorRuleDetector.getStats(); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,7 @@ import { | |
| import type { | ||
| ModelPrice, | ||
| ModelPriceData, | ||
| ModelPriceSource, | ||
| PriceTableJson, | ||
| PriceUpdateResult, | ||
| SyncConflict, | ||
|
|
@@ -98,13 +99,17 @@ function buildManualPriceDataFromProviderPricing( | |
|
|
||
| /** | ||
| * 价格表处理核心逻辑(内部函数,无权限检查) | ||
| * 用于系统初始化和 Web UI 上传 | ||
| * 用于系统初始化、云端自动同步和 Web UI 上传 | ||
| * @param jsonContent - 价格表 JSON 内容 | ||
| * @param overwriteManual - 可选,要覆盖的手动添加模型名称列表 | ||
| * @param source - 写入记录的来源。云端/自动同步为 'litellm'(默认); | ||
| * 用户在本地显式上传的价格表为 'manual',使其遵循“本地优先”原则、 | ||
| * 不被后续云端自动同步覆盖。 | ||
| */ | ||
| export async function processPriceTableInternal( | ||
| jsonContent: string, | ||
| overwriteManual?: string[] | ||
| overwriteManual?: string[], | ||
| source: ModelPriceSource = "litellm" | ||
| ): Promise<ActionResult<PriceUpdateResult>> { | ||
| try { | ||
| // 解析JSON内容 | ||
|
|
@@ -156,7 +161,10 @@ export async function processPriceTableInternal( | |
| }; | ||
|
|
||
| // 处理每个模型的价格 | ||
| for (const [modelName, priceData] of entries) { | ||
| for (const [rawModelName, priceData] of entries) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Performing individual database transactions ( |
||
| // 与 manual 记录入库时(upsertModelPrice 使用 trim 后的名称)保持一致地归一化, | ||
| // 避免云端表里带空白的同名键绕过本地手动模型的保护检查。 | ||
| const modelName = typeof rawModelName === "string" ? rawModelName.trim() : rawModelName; | ||
| try { | ||
| // 验证价格数据基本类型 | ||
| if (typeof priceData !== "object" || priceData === null) { | ||
|
|
@@ -172,9 +180,11 @@ export async function processPriceTableInternal( | |
| continue; | ||
| } | ||
|
|
||
| // 检查是否存在手动添加的价格且不在覆盖列表中 | ||
| // 本地优先:仅当本次写入来自云端/自动同步(source='litellm')时, | ||
| // 才跳过用户手动维护的模型,除非显式列入覆盖列表。 | ||
| // 用户显式上传(source='manual')属于权威导入,不受此保护跳过,可正常覆盖。 | ||
| const isManualPrice = manualPrices.has(modelName); | ||
| if (isManualPrice && !overwriteSet.has(modelName)) { | ||
| if (source === "litellm" && isManualPrice && !overwriteSet.has(modelName)) { | ||
| // 跳过手动添加的模型,记录到 skippedConflicts | ||
| result.skippedConflicts?.push(modelName); | ||
| result.unchanged.push(modelName); | ||
|
|
@@ -186,15 +196,15 @@ export async function processPriceTableInternal( | |
|
|
||
| if (!existingPrice) { | ||
| // 模型不存在,新增记录 | ||
| await createModelPrice(modelName, priceData, "litellm"); | ||
| await createModelPrice(modelName, priceData, source); | ||
| result.added.push(modelName); | ||
| } else if (!isPriceDataEqual(existingPrice.priceData, priceData)) { | ||
| // 模型存在但价格发生变化 | ||
| // 如果是手动模型且在覆盖列表中,先删除旧记录 | ||
| if (isManualPrice && overwriteSet.has(modelName)) { | ||
| await deleteModelPriceByName(modelName); | ||
| } | ||
| await createModelPrice(modelName, priceData, "litellm"); | ||
| } else if ( | ||
| existingPrice.source !== source || | ||
| !isPriceDataEqual(existingPrice.priceData, priceData) | ||
| ) { | ||
| // 价格或来源发生变化:用事务原子地“删旧 + 插新”替换该模型的所有记录, | ||
| // 既保证不会在崩溃时丢失价格,又避免同名记录堆积(litellm 孤儿行,或 manual + litellm 并存)。 | ||
| await upsertModelPrice(modelName, priceData, source); | ||
| result.updated.push(modelName); | ||
| } else { | ||
| // 价格未发生变化,不需要更新 | ||
|
|
@@ -261,7 +271,9 @@ export async function uploadPriceTable( | |
| jsonContent = JSON.stringify(parseResult.data.models); | ||
| } | ||
|
|
||
| const result = await processPriceTableInternal(jsonContent, overwriteManual); | ||
| // 用户显式上传的价格表视为“本地优先”的权威来源,标记为 manual, | ||
| // 使其不会被后续云端自动同步静默覆盖。 | ||
| const result = await processPriceTableInternal(jsonContent, overwriteManual, "manual"); | ||
|
|
||
| if (result.ok) { | ||
| emitActionAudit({ | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,7 +3,6 @@ | |||||||||||||||||||||||||
| import { emitActionAudit } from "@/lib/audit/emit"; | ||||||||||||||||||||||||||
| import { getSession } from "@/lib/auth"; | ||||||||||||||||||||||||||
| import type { NotificationJobType } from "@/lib/constants/notification.constants"; | ||||||||||||||||||||||||||
| import { logger } from "@/lib/logger"; | ||||||||||||||||||||||||||
| import { resolveSystemTimezone } from "@/lib/utils/timezone"; | ||||||||||||||||||||||||||
| import { WebhookNotifier } from "@/lib/webhook"; | ||||||||||||||||||||||||||
| import { buildTestMessage } from "@/lib/webhook/templates/test-messages"; | ||||||||||||||||||||||||||
|
|
@@ -41,18 +40,10 @@ export async function updateNotificationSettingsAction( | |||||||||||||||||||||||||
| const before = await getNotificationSettings(); | ||||||||||||||||||||||||||
| const updated = await updateNotificationSettings(payload); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // 重新调度通知任务(仅生产环境) | ||||||||||||||||||||||||||
| if (process.env.NODE_ENV === "production") { | ||||||||||||||||||||||||||
| // 动态导入避免 Turbopack 编译 Bull 模块 | ||||||||||||||||||||||||||
| const { scheduleNotifications } = await import("@/lib/notification/notification-queue"); | ||||||||||||||||||||||||||
| await scheduleNotifications(); | ||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||
| logger.warn({ | ||||||||||||||||||||||||||
| action: "schedule_notifications_skipped", | ||||||||||||||||||||||||||
| reason: "development_mode", | ||||||||||||||||||||||||||
| message: "Notification scheduling is disabled in development mode", | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| // 重新调度通知任务,使总开关、子开关、时间/间隔等变更立即生效(添加/移除 repeatable 作业)。 | ||||||||||||||||||||||||||
| // 动态导入避免静态加载 Bull;scheduleNotifications 内部已 fail-open,缺少 REDIS_URL 时不会影响设置保存。 | ||||||||||||||||||||||||||
| const { scheduleNotifications } = await import("@/lib/notification/notification-queue"); | ||||||||||||||||||||||||||
| await scheduleNotifications(); | ||||||||||||||||||||||||||
|
Comment on lines
+43
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 将调度调用改为真正的 fail-open,避免“设置已保存但返回失败” 这里如果 建议修改- const { scheduleNotifications } = await import("`@/lib/notification/notification-queue`");
- await scheduleNotifications();
+ try {
+ const { scheduleNotifications } = await import("`@/lib/notification/notification-queue`");
+ await scheduleNotifications();
+ } catch {
+ // fail-open: 调度失败不影响设置保存结果
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| emitActionAudit({ | ||||||||||||||||||||||||||
| category: "notification", | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
在 Node.js 官方文档中确认:node:zlib的 zstd 同步解压(如zstdDecompressSync)从哪个 Node 版本开始可用?Node 22.0.0 是否保证可用?💡 Result:
在 Node.js 中,
node:zlib模块提供的 Zstd 同步解压方法zstdDecompressSync是从 Node.js v22.15.0 版本开始可用的 [1][2]。 关于您询问的 Node 22.0.0 版本:该功能在 Node 22.0.0 中不可用 [2]。zstdDecompressSync直到后续的补丁版本 v22.15.0 才被正式加入到 API 中 [1][2]。如果您需要在 Node 22.x 系列中使用此功能,请确保您的 Node.js 版本至少升级到 v22.15.0 或更高版本。Citations:
CI Node 版本应固定到明确下限(22.15.x)
当前
.github/workflows/pr-check.yml将node-version固定为"22",可能解析到 22.15.0 之前的 22.x;而node:zlib的zstdDecompressSync需要至少 Node.js v22.15.0 才提供(22.0.0 不包含该能力)。为避免 CI 运行时漂移,建议固定到22.15.x(或更高)。建议修改
🤖 Prompt for AI Agents