chore: 忽略 electron-userdata,更新 agent-core 源码

This commit is contained in:
mark
2026-09-27 20:11:03 +08:00
parent 364e6eb1a4
commit d90f6491dd
130 changed files with 170 additions and 33881 deletions
+9 -5
View File
@@ -13,12 +13,16 @@ export { llmConfigManager } from './llm/config'
// ============ 启动配置 ============
const WS_PORT = Number(process.env.AGENT_CORE_PORT) || 8787
// WebSocket 监听在 UDS / 命名管道上,不占用 TCP 端口
const IS_WIN = process.platform === 'win32'
const SOCKET_PATH =
process.env.AGENT_CORE_SOCKET ||
(IS_WIN ? '\\\\.\\pipe\\dndclaw-agent-core' : '/tmp/dndclaw-agent-core.sock')
const DB_DIR = process.env.AGENT_CORE_DB_DIR || './data'
/**
* agent-core 入口
* 启动 SQLite + WebSocket 服务器
* 启动 SQLite + WebSocket 服务器(UDS/命名管道)
*/
function main() {
console.log('========================================')
@@ -29,9 +33,9 @@ function main() {
initDatabase(DB_DIR)
console.log(`[core] 数据库目录: ${DB_DIR}`)
// 2. 启动 WebSocket
const wss = startWebSocketServer(WS_PORT)
console.log(`[core] WebSocket 端口: ${WS_PORT}`)
// 2. 启动 WebSocket(监听 UDS/命名管道)
const wss = startWebSocketServer(SOCKET_PATH)
console.log(`[core] WebSocket 管道: ${SOCKET_PATH}`)
// 3. 启动云端同步 Worker(本地变更 → outbox → 云端推送;云端变更 → 拉取合并)
syncWorker.start()
+88 -29
View File
@@ -4,10 +4,8 @@ import type { OutboxRecord } from './outbox'
* 云端同步客户端
*
* 云端地址通过环境变量配置:
* CLOUD_SYNC_URL - 云端同步服务地址(如 http://localhost:9000)
* CLOUD_SYNC_URL - 云端同步服务地址(如 https://service.hotblaz.com/api/data)
* CLOUD_SYNC_TOKEN - 鉴权 Token(可选)
*
* 端口留空,等用户提供后通过环境变量传入即可。
*/
const CLOUD_URL = process.env.CLOUD_SYNC_URL || ''
@@ -33,52 +31,113 @@ export interface CloudPullResult {
/**
* 推送一批 outbox 记录到云端
*
* 按表分组,使用对应表的批量同步接口:
* - llm_configs → POST {CLOUD_URL}/llm-configs/sync(幂等 upsert + 批量)
* - 其他表 → 暂未实现云端批量接口,标记失败
*/
export async function pushToCloud(records: OutboxRecord[]): Promise<Map<string, CloudPushResult>> {
const results = new Map<string, CloudPushResult>()
if (!CLOUD_URL) {
// 云端地址未配置,全部标记失败
for (const r of records) {
results.set(r.id, { success: false, error: 'CLOUD_SYNC_URL 未配置' })
}
return results
}
// 按表分组
const grouped = new Map<string, OutboxRecord[]>()
for (const r of records) {
try {
const res = await fetch(`${CLOUD_URL}/sync/push`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(CLOUD_TOKEN ? { Authorization: `Bearer ${CLOUD_TOKEN}` } : {})
},
body: JSON.stringify({
table: r.table_name,
record_id: r.record_id,
operation: r.operation,
payload: JSON.parse(r.payload),
local_updated_at: r.updated_at
})
})
const list = grouped.get(r.table_name) || []
list.push(r)
grouped.set(r.table_name, list)
}
if (res.ok) {
results.set(r.id, { success: true })
} else {
const data = await res.json().catch(() => ({}))
results.set(r.id, {
success: false,
error: `HTTP ${res.status}: ${data?.message || res.statusText}`
})
for (const [tableName, tableRecords] of grouped) {
if (tableName === 'llm_configs') {
await pushLLMConfigs(tableRecords, results)
} else {
// 其他表暂无云端批量接口
for (const r of tableRecords) {
results.set(r.id, { success: false, error: `云端暂不支持表 ${tableName} 的同步` })
}
} catch (e: any) {
results.set(r.id, { success: false, error: e?.message || String(e) })
}
}
return results
}
/**
* 推送 llm_configs 变更到云端批量同步接口
*
* 云端接口:POST {CLOUD_URL}/llm-configs/sync
* 请求体:{ items: [{ id, name, provider, config, isActive, operation }] }
* operation: 'upsert' | 'delete'
*/
async function pushLLMConfigs(
records: OutboxRecord[],
results: Map<string, CloudPushResult>
) {
const items = records.map((r) => {
const payload = JSON.parse(r.payload || '{}')
const operation = r.operation === 'DELETE' ? 'delete' : 'upsert'
return {
id: r.record_id,
name: payload.name,
provider: payload.provider,
config: payload.config,
isActive: payload.is_active === 1 || payload.isActive === true,
operation
}
})
try {
const res = await fetch(`${CLOUD_URL}/llm-configs/sync`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(CLOUD_TOKEN ? { Authorization: `Bearer ${CLOUD_TOKEN}` } : {})
},
body: JSON.stringify({ items })
})
if (!res.ok) {
const data = await res.json().catch(() => ({}))
const errorMsg = `HTTP ${res.status}: ${data?.error || res.statusText}`
for (const r of records) {
results.set(r.id, { success: false, error: errorMsg })
}
return
}
const data = await res.json()
// 云端返回 { ok, total, results: [{ id, status, reason }] }
const cloudResults: Array<{ id: string; status: string; reason?: string }> = data?.results || []
const resultMap = new Map(cloudResults.map((cr) => [cr.id, cr]))
for (const r of records) {
const cr = resultMap.get(r.record_id)
if (cr && (cr.status === 'created' || cr.status === 'updated' || cr.status === 'deleted')) {
results.set(r.id, { success: true })
} else if (cr && cr.status === 'skipped') {
// 跳过(如删除不存在的记录),视为成功
results.set(r.id, { success: true })
} else {
results.set(r.id, {
success: false,
error: cr?.reason || `云端返回状态: ${cr?.status || 'unknown'}`
})
}
}
} catch (e: any) {
const errorMsg = e?.message || String(e)
for (const r of records) {
results.set(r.id, { success: false, error: errorMsg })
}
}
}
/**
* 从云端拉取变更记录(增量)
* @param cursor 上次拉取的游标,为空则全量拉取
+21 -5
View File
@@ -1,3 +1,4 @@
import { createServer } from 'http'
import { WebSocketServer, WebSocket } from 'ws'
import { llmConfigManager } from '../llm/config'
import type { LLMConfig } from '../types'
@@ -17,13 +18,28 @@ interface WSMessage {
const clients = new Set<WebSocket>()
/**
* 启动 WebSocket 服务器
* @param port 端口号
* 启动 WebSocket 服务器(监听在 UDS / 命名管道上,不占用 TCP 端口)
*
* - Windows: 命名管道 \\.\pipe\dndclaw-agent-core
* - Linux/Mac: UDS socket 文件
*
* @param socketPath 命名管道或 UDS socket 路径
*/
export function startWebSocketServer(port: number): WebSocketServer {
const wss = new WebSocketServer({ port })
export function startWebSocketServer(socketPath: string): WebSocketServer {
const server = createServer()
const wss = new WebSocketServer({ server })
console.log(`[ws] WebSocket 服务器启动于 ws://localhost:${port}`)
server.listen(socketPath, () => {
console.log(`[ws] WebSocket 服务器启动于 ${socketPath}`)
})
server.on('error', (err: any) => {
if (err.code === 'EADDRINUSE') {
console.error(`[ws] 管道已被占用: ${socketPath}`)
} else {
console.error('[ws] 服务器错误:', err.message)
}
})
wss.on('connection', (ws) => {
clients.add(ws)