Skip to content
Merged
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## 1.3.207 — 2026-10-21

- 设置导航里 OpenAI 只留一个入口(调度组)。去掉「白名单」页和 `persona_rules` 整套:网关不再按末轮 user 正则追加防泄漏 / 身份 / 无工具提示,park 与 overlay 只带 `persona_standing`,overlay 模板去掉 `{{rules}}`。保存协议时 `routing.json` 里的 `persona_rules`、`persona_leak_append` 一并删除。
- 设置 → 遥测重做:槽位分布条、每槽实时旁路状态(运行中的槽每 20 秒探测)、按槽开关,旁路重启失败单独提示;展开可看上传时间线(批次、GrowthBook、失败)和脱敏原始记录。「上传中」要求 `worker.json` 遥测生效且进程在跑;种子开但没有官方身份显示「开 · 无官方身份」,集群槽注明。全开 / 全关先确认并逐槽显示进度,GPT 槽不参与批量写入。附播种写入键对照、上传链路说明,以及「换票后同步遥测」「Codex analytics」的当前值和跳转。
- 遥测上传间隔可调:`routing.telemetry.upload_interval_seconds`,整分钟、1 分钟到 24 小时,默认 10 分钟(原固定 10 秒)。设置 → 遥测可按分钟或小时设置,保存后重写各 Claude 槽 `worker.json`,旁路热重载,不重启;集群槽改为排队推送到节点。GrowthBook 仍是 6 小时,10 分钟会话窗口不变。
- 遥测旁路读官方身份时不再要求 `machineID`。当前 CLI 初装只写 `userID`,这类槽原先一直卡在 `waiting_official_identity`,不上传也没有记录。`.claude.json` 都缺失时,改从槽记录的 `fingerprint.official_user_id` 取身份。GPT 槽不写 Claude 遥测身份。

已部署机升级:更新 Node 控制面、`web/dist` 与 `bin/kin-worker`,重启一次 Node。无迁移、无新依赖,不必 `wrap-cli/sync`。上传间隔要新的 `kin-worker` 才生效:本机槽需重启遥测旁路(fleet `action=telemetry`);集群槽在下次槽位启动、重载或出口重建时推送新的 `kin-worker`。旧旁路忽略该设置,仍每 10 秒上传。卡在 `waiting_official_identity` 的本机槽,播种或保存一次种子后 `worker.json` 会写入身份。不要覆盖 `routing.json`、`vms/`、`data/`、`.env`。

## 1.3.206 — 2026-10-21

- 官方登录终端把授权链接显式提取出来,跨终端帧也不会漏。复制链接、打开授权页和回填授权码与普通 OAuth 相同。
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.3.206
1.3.207
2 changes: 2 additions & 0 deletions src/lib/admin/panel-api.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ import { effectiveOpenAIPolicy, normalizeOpenAIQuotaPolicy } from '../pool/opena
import { openAIRuntimeSignals } from '../pool/openai-account-runtime.mjs'
import { normalizeVmConfig, memoryMiB } from '../vm/machine-spec.mjs'
import { runtimeKind } from '../vm/runtime-kind.mjs'
import { telemetryUploadInterval } from '../vm/worker-telemetry.mjs'

export function ok(data, meta) {
const out = { ok: true, data }
Expand Down Expand Up @@ -1443,6 +1444,7 @@ export function buildRouting({ routingConfig, stickyRouter }) {
usage_probe: normalizeUsageProbeConfig(routingConfig?.usage_probe),
notify: publicNotifyConfig(routingConfig?.notify),
vm: normalizeVmConfig(routingConfig?.vm),
telemetry: { upload_interval_seconds: telemetryUploadInterval(routingConfig) },
sessions: stickyRouter.stats(),
})
}
Expand Down
7 changes: 6 additions & 1 deletion src/lib/admin/panel-routes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ import {
SESSION_SLOT_MIN,
validateInferenceRoutingPatch,
} from '../vm/slot-engine.mjs'
import { validateTelemetryRoutingPatch } from '../vm/worker-telemetry.mjs'
import { makeError, ErrorType, ErrorCode } from '../core/errors.mjs'
import * as panel from './panel-api.mjs'
import { GATEWAY_CAPABILITIES } from '../vm/execution-context.mjs'
Expand Down Expand Up @@ -5209,7 +5210,11 @@ export function createPanelHandler(ctx) {
})
}
}
const personaProblems = [...panel.validatePersonaRoutingPatch(body), ...validateInferenceRoutingPatch(body)]
const personaProblems = [
...panel.validatePersonaRoutingPatch(body),
...validateInferenceRoutingPatch(body),
...validateTelemetryRoutingPatch(body),
]
if (personaProblems.length) {
return json(res, 400, {
ok: false,
Expand Down
38 changes: 38 additions & 0 deletions src/lib/admin/routing-runtime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ import { isCodexVm } from '../vm/vm-kind.mjs'
import { syncCodexQuotaSchedule } from '../vm/vm-registry.mjs'
import { wakeOpenAIWaiter } from '../pool/openai-account-runtime.mjs'
import { normalizeVmConfig } from '../vm/machine-spec.mjs'
import { syncWorkerTelemetry } from '../vm/vm-runtime.mjs'
import { slotHost } from '../vm/slot-host.mjs'
import { telemetryUploadInterval } from '../vm/worker-telemetry.mjs'

export function createRoutingRuntime(ctx) {
const getRouting = () => (typeof ctx.getRoutingConfig === 'function' ? ctx.getRoutingConfig() : ctx.routingConfig)
Expand Down Expand Up @@ -404,6 +407,28 @@ export function createRoutingRuntime(ctx) {
return queued
}

// The sidecar re-reads worker.json on mtime change, so a rewrite applies without a restart.
// Node slots read a pushed copy; that push is queued and only logged on failure.
function syncTelemetryUploadInterval(routingConfig) {
const project = ctx.cfg.paths.project
const applied = { updated: 0, queued: 0, skipped: 0, failed: [] }
for (const { id } of listVms(project)) {
const vm = getVm(project, id)
if (!vm || isCodexVm(vm)) continue
try {
if (!syncWorkerTelemetry(vm, project, routingConfig).wrote) {
applied.skipped += 1
continue
}
if (slotHost(vm).queueSyncRun(vm, path.join(project, 'vms', vm.id))) applied.queued += 1
else applied.updated += 1
} catch {
applied.failed.push(vm.id)
}
}
return applied
}

function persistRoutingPatch(body = {}) {
const hasCodexQuota = body?.codex && Object.prototype.hasOwnProperty.call(body.codex, 'quota')
if (hasCodexQuota) validateOpenAIQuotaPatch(body.codex.quota)
Expand All @@ -416,6 +441,7 @@ export function createRoutingRuntime(ctx) {
const prevTiers = routingConfig.tiers
const prevIdleMs = streamIdleTimeoutMs(routingConfig)
const prevRequestBytes = maxRequestBytes(routingConfig)
const prevUploadInterval = telemetryUploadInterval(routingConfig)
setRouting(routingConfig)
if (body.sticky) routingConfig.sticky = { ...(routingConfig.sticky || {}), ...body.sticky }
if (body.quota) routingConfig.quota = { ...(routingConfig.quota || {}), ...body.quota }
Expand All @@ -434,6 +460,8 @@ export function createRoutingRuntime(ctx) {
const nextSessionSlots = normalizeSessionSlots(routingConfig.inference?.session_slots)
if (body.compatibility) {
routingConfig.compatibility = { ...(routingConfig.compatibility || {}), ...body.compatibility }
delete routingConfig.compatibility.persona_rules
delete routingConfig.compatibility.persona_leak_append
if (
Object.prototype.hasOwnProperty.call(body.compatibility, 'cch_enabled') ||
routingConfig.compatibility.cch_enabled == null
Expand Down Expand Up @@ -462,6 +490,12 @@ export function createRoutingRuntime(ctx) {
}
routingConfig.vm = normalizeVmConfig(next)
}
if (body.telemetry && Object.hasOwn(body.telemetry, 'upload_interval_seconds')) {
routingConfig.telemetry = {
...(routingConfig.telemetry || {}),
upload_interval_seconds: telemetryUploadInterval({ telemetry: body.telemetry }),
}
}
applyOfficialCcRoutingBody(body, prevOfficialCc)
applyHealthProbeRoutingBody(body, prevHealthProbe)
applyUsageProbeRoutingBody(body, prevUsageProbe)
Expand Down Expand Up @@ -499,6 +533,8 @@ export function createRoutingRuntime(ctx) {
: null
// Stream idle / body-limit still need an idle restart; CCH and sticky-slot TTL hot-read kernel.json.
const kernelReload = idleChanged || requestBytesChanged ? reloadClaudeKernelsWhenIdle(routingConfig) : 0
const telemetrySync =
telemetryUploadInterval(routingConfig) !== prevUploadInterval ? syncTelemetryUploadInterval(routingConfig) : null
// Pool and failover settings hot-reload into the live scheduler and runner.
// Rebuilding them would drop the seat book and queues while their requests
// still run (the panel sends `failover` with every routing save).
Expand All @@ -517,6 +553,7 @@ export function createRoutingRuntime(ctx) {
: { concurrency: 0, rpm: 0, sessions: 0, skipped: 0 },
kernel_persona: kernelPersona,
kernel_reload: kernelReload,
telemetry_sync: telemetrySync,
}
} catch (err) {
console.error(
Expand All @@ -530,6 +567,7 @@ export function createRoutingRuntime(ctx) {
rpm: { skipped: 0 },
kernel_persona: kernelPersona,
kernel_reload: kernelReload,
telemetry_sync: telemetrySync,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/identity/crs-persona-usage.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ function stripKnownTexts(text, known) {
return rest.replace(/^\s+|\s+$/g, '').replace(/\n{3,}/g, '\n\n')
}

/** Rule-append / overlay text we injected; caller leftover is not included. */
/** Overlay text we injected; caller leftover is not included. */
export function injectedOverlayText(before, after) {
if (!after || before === after) return ''
const beforeSys = extractSystemTexts(before?.system)
Expand Down
Loading
Loading