feat: enhance session sync and add voice input feature

- 增强会话同步:确保所有会话完全同步到新账号
  - 强制更新 model_provider 字段
  - 添加 session_index.jsonl 重建功能
  - 确保数据库 UPSERT 逻辑正确处理所有情况
- 新增语音输入功能(voice_cdp.js, voice_inject.js)
- 测试脚本和增强模块

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
mac
2026-07-16 19:16:45 +08:00
parent a6207fdefd
commit 02fb553c9e
19 changed files with 1884 additions and 95 deletions
+17 -3
View File
@@ -15,6 +15,7 @@ const sessions = require("./lib/sessions");
const providerSync = require("./lib/provider_sync");
const router = require("./lib/router");
const canvas = require("./lib/canvas");
const voice = require("./lib/voice_cdp");
const proxy = require("./lib/proxy");
const memory = require("./lib/memory");
const { extractZip } = require("./lib/zip");
@@ -729,8 +730,7 @@ function skillCatalog() {
async function skillInstall(skill) {
const url = skill.origin;
if (!url) throw new Error("该技能没有可用的下载地址");
const safeName = String(skill.name || "skill")
.replace(/[^\w一-龥.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "skill";
const safeName = safeSkillDir(skill.name);
const storeDir = path.join(SKILL_STORE, safeName);
const codexLink = path.join(codex.CODEX_HOME, "skills", safeName);
log(`下载技能 ${skill.name} …`);
@@ -850,9 +850,10 @@ function readSkillMeta(skillDir, fallbackName) {
return meta;
}
// 把名称规整成磁盘技能目录名(与 skillInstall 的 safeName / renderer 的 safeSkillDir 同算法)
// 把名称规整成磁盘技能目录名(与 renderer 的 safeSkillDir 同算法)。统一小写,避免同一技能的大小写变体重复安装、软链名与实体名错位。
function safeSkillDir(name) {
return (String(name || "skill")
.toLowerCase()
.replace(/[^\w一-龥.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80)) || "skill";
}
@@ -1330,6 +1331,19 @@ ipcMain.handle("canvas-run", (e, { kind, params }) =>
ipcMain.handle("canvas-groups", () => canvas.groups());
ipcMain.handle("canvas-models", (_e, { groupId }) => canvas.listModels(groupId));
// —— Codex 语音输入注入(CDP)——
ipcMain.handle("voice-status", () => voice.status());
ipcMain.handle("voice-connect", (e, opts) =>
voice.connect((s) => {
try {
e.sender.send("voice-status", s);
} catch {}
}, opts || {})
);
ipcMain.handle("voice-disconnect", () => voice.disconnect());
ipcMain.handle("voice-stt-info", () => voice.sttInfo());
ipcMain.handle("voice-probe-stt", async () => voice.probeStt());
// —— 全局 / 系统代理 ——
ipcMain.handle("proxy-status", async () => proxy.status());
ipcMain.handle("proxy-get-config", async () => proxy.getConfig());