feat: auto-sync bound codex threads into conversations
This commit is contained in:
@@ -5,13 +5,64 @@ import { createServer } from "node:http";
|
||||
import { access, readFile, readdir, rm } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { discoverCodexProjectCandidates } from "./codex-session-discovery.mjs";
|
||||
|
||||
async function loadConfig(configPath) {
|
||||
const raw = await readFile(resolve(configPath), "utf8");
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
|
||||
async function postHeartbeat(config, runtime) {
|
||||
async function resolveHeartbeatProjects(config, runtime) {
|
||||
const staticProjects = Array.isArray(config.projects) ? config.projects : [];
|
||||
const staticCandidates = Array.isArray(config.projectCandidates) ? config.projectCandidates : [];
|
||||
if (config.codexSessionDiscoveryEnabled === false) {
|
||||
return {
|
||||
projects: staticProjects,
|
||||
projectCandidates: staticCandidates,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const discovered = await discoverCodexProjectCandidates({
|
||||
stateDbPath: config.codexStateDbPath,
|
||||
logsDbPath: config.codexLogsDbPath,
|
||||
sessionIndexPath: config.codexSessionIndexPath,
|
||||
globalStatePath: config.codexGlobalStatePath,
|
||||
sessionsDir: config.codexSessionsDir,
|
||||
lookbackHours: config.codexSessionLookbackHours,
|
||||
});
|
||||
const candidateMap = new Map();
|
||||
for (const candidate of [...staticCandidates, ...discovered.projectCandidates]) {
|
||||
candidateMap.set(candidate.codexThreadRef ?? candidate.threadId, candidate);
|
||||
}
|
||||
const mergedCandidates = [...candidateMap.values()];
|
||||
const mergedProjects = [...new Set([...staticProjects, ...discovered.projects, ...mergedCandidates.map((candidate) => candidate.folderName)])];
|
||||
runtime.lastProjectDiscoveryAt = new Date().toISOString();
|
||||
runtime.lastProjectDiscoveryOk = true;
|
||||
runtime.lastProjectDiscoverySummary = `${mergedCandidates.length} threads / ${mergedProjects.length} folders`;
|
||||
return {
|
||||
projects: mergedProjects,
|
||||
projectCandidates: mergedCandidates,
|
||||
};
|
||||
} catch (error) {
|
||||
runtime.lastProjectDiscoveryAt = new Date().toISOString();
|
||||
runtime.lastProjectDiscoveryOk = false;
|
||||
runtime.lastProjectDiscoverySummary = error instanceof Error ? error.message : String(error);
|
||||
await postAppLog(config, runtime, {
|
||||
level: "error",
|
||||
category: "local_agent.codex_discovery_failed",
|
||||
message: "Codex 线程扫描失败,已退回静态项目配置。",
|
||||
detail: runtime.lastProjectDiscoverySummary,
|
||||
mirrorToMaster: true,
|
||||
});
|
||||
return {
|
||||
projects: staticProjects,
|
||||
projectCandidates: staticCandidates,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function postHeartbeat(config, runtime, heartbeatProjects) {
|
||||
const response = await fetch(`${config.controlPlaneUrl.replace(/\/$/, "")}/api/device-heartbeat`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
@@ -25,8 +76,8 @@ async function postHeartbeat(config, runtime) {
|
||||
status: config.status,
|
||||
quota5h: config.quota5h,
|
||||
quota7d: config.quota7d,
|
||||
projects: config.projects,
|
||||
projectCandidates: config.projectCandidates,
|
||||
projects: heartbeatProjects.projects,
|
||||
projectCandidates: heartbeatProjects.projectCandidates,
|
||||
endpoint: config.endpoint,
|
||||
}),
|
||||
});
|
||||
@@ -454,11 +505,15 @@ const runtime = {
|
||||
masterTaskBusy: false,
|
||||
activeMasterTask: null,
|
||||
lastMasterTaskPoll: null,
|
||||
lastProjectDiscoveryAt: null,
|
||||
lastProjectDiscoveryOk: false,
|
||||
lastProjectDiscoverySummary: null,
|
||||
};
|
||||
|
||||
async function heartbeat() {
|
||||
try {
|
||||
const result = await postHeartbeat(config, runtime);
|
||||
const heartbeatProjects = await resolveHeartbeatProjects(config, runtime);
|
||||
const result = await postHeartbeat(config, runtime, heartbeatProjects);
|
||||
runtime.lastHeartbeatAt = new Date().toISOString();
|
||||
runtime.lastHeartbeatOk = result.ok;
|
||||
runtime.lastHeartbeatStatus = result.status;
|
||||
|
||||
Reference in New Issue
Block a user