mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-05 08:54:23 +08:00
feat(agent): upgrade Codex to version 0.146.0 and implement version checks at startup
This commit is contained in:
@@ -2,6 +2,7 @@ import { spawn, type ChildProcess } from "node:child_process";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import stripAnsi from "strip-ansi";
|
||||
|
||||
import { VERSION } from "../config.js";
|
||||
import { logger } from "../utils/logger.js";
|
||||
@@ -43,7 +44,7 @@ export class CodexAppClient {
|
||||
const client = new CodexAppClient(child, emit);
|
||||
child.stdout?.on("data", (chunk) => client.read(chunk.toString()));
|
||||
child.stderr?.on("data", (chunk) => {
|
||||
const text = chunk.toString();
|
||||
const text = stripAnsi(chunk.toString()).replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s+/, "");
|
||||
logger.warn("Codex app-server stderr", { text });
|
||||
emit("agent_log", { text });
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { AgentAttachment, AgentPermissionMode } from "../agent/types.js";
|
||||
import { CanvasSession } from "../canvas/session.js";
|
||||
import { DEFAULT_PORT, ensureSiteWorkspace, loadConfig, saveConfig, updateSiteWorkspace, type CanvasAgentConfig } from "../config.js";
|
||||
import { logger } from "../utils/logger.js";
|
||||
import { checkVersions } from "../version-check.js";
|
||||
|
||||
/** 启动仅监听本机的 Canvas Agent HTTP 服务。 */
|
||||
export function startHttpServer() {
|
||||
@@ -223,6 +224,7 @@ export function startHttpServer() {
|
||||
|
||||
app.listen(port, "127.0.0.1", () => {
|
||||
console.log("Infinite Canvas Agent");
|
||||
checkVersions();
|
||||
console.log(`Local URL: ${config.url}`);
|
||||
console.log(`Connect token: ${config.token}`);
|
||||
console.log("Codex MCP is not installed by this command.");
|
||||
|
||||
@@ -11,47 +11,44 @@ import {formatDateForFilename} from "./date.js";
|
||||
export class Logger {
|
||||
readonly enabled = process.argv.includes("--debug");
|
||||
readonly filePath = this.enabled ? path.join(os.homedir(), ".infinite-canvas", "logs", `canvas-agent-${formatDateForFilename()}.log`) : "";
|
||||
private readonly logger: WinstonLogger | null;
|
||||
private readonly logger: WinstonLogger;
|
||||
|
||||
/** 根据命令行 Debug 参数初始化日志输出。 */
|
||||
/** 普通模式输出 Info 以上日志,Debug 模式额外输出 Debug 并写入文件。 */
|
||||
constructor() {
|
||||
if (!this.enabled) {
|
||||
this.logger = null;
|
||||
return;
|
||||
}
|
||||
fs.mkdirSync(path.dirname(this.filePath), {recursive: true});
|
||||
const line = format.printf(({level, message, timestamp, details}) => `${timestamp} ${level.toUpperCase()} ${message}${formatDetails(details)}`);
|
||||
const output = format.combine(format.timestamp({format: "YYYY-MM-DD HH:mm:ss"}), line);
|
||||
if (this.enabled) fs.mkdirSync(path.dirname(this.filePath), {recursive: true});
|
||||
this.logger = winston.createLogger({
|
||||
level: "debug",
|
||||
level: this.enabled ? "debug" : "info",
|
||||
transports: [
|
||||
new transports.Console({format: format.combine(format.timestamp({format: "HH:mm:ss"}), line)}),
|
||||
new transports.File({filename: this.filePath, format: format.combine(format.timestamp({format: "HH:mm:ss"}), line)}),
|
||||
new transports.Console({format: output}),
|
||||
...(this.enabled ? [new transports.File({filename: this.filePath, format: output})] : []),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/** 输出 Debug 级别日志。 */
|
||||
debug(message: string, details?: unknown) {
|
||||
if (details === undefined) this.logger?.debug(message);
|
||||
else this.logger?.debug(message, {details: sanitize(details)});
|
||||
if (details === undefined) this.logger.debug(message);
|
||||
else this.logger.debug(message, {details: sanitize(details)});
|
||||
}
|
||||
|
||||
/** 输出 Info 级别日志。 */
|
||||
info(message: string, details?: unknown) {
|
||||
if (details === undefined) this.logger?.info(message);
|
||||
else this.logger?.info(message, {details: sanitize(details)});
|
||||
if (details === undefined) this.logger.info(message);
|
||||
else this.logger.info(message, {details: sanitize(details)});
|
||||
}
|
||||
|
||||
/** 输出 Warn 级别日志。 */
|
||||
warn(message: string, details?: unknown) {
|
||||
if (details === undefined) this.logger?.warn(message);
|
||||
else this.logger?.warn(message, {details: sanitize(details)});
|
||||
if (details === undefined) this.logger.warn(message);
|
||||
else this.logger.warn(message, {details: sanitize(details)});
|
||||
}
|
||||
|
||||
/** 输出 Error 级别日志。 */
|
||||
error(message: string, details?: unknown) {
|
||||
if (details === undefined) this.logger?.error(message);
|
||||
else this.logger?.error(message, {details: sanitize(details)});
|
||||
if (details === undefined) this.logger.error(message);
|
||||
else this.logger.error(message, {details: sanitize(details)});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { execFile, execFileSync } from "node:child_process";
|
||||
import { createRequire } from "node:module";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
import { VERSION } from "./config.js";
|
||||
import { logger } from "./utils/logger.js";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const execFileAsync = promisify(execFile);
|
||||
const CODEX_VERSION = String((require("@openai/codex/package.json") as { version: string }).version);
|
||||
|
||||
/** 输出当前版本,并在后台检查 npm 最新版本。 */
|
||||
export function checkVersions() {
|
||||
const localCodexVersion = commandVersion("codex");
|
||||
logger.info("Canvas Agent version", { version: VERSION });
|
||||
logger.info("Bundled Codex version", { version: CODEX_VERSION });
|
||||
logger.info("Local Codex version", { version: localCodexVersion || "not found" });
|
||||
if (!localCodexVersion) {
|
||||
logger.warn("Local Codex was not found. Install the latest version with: npm install -g @openai/codex@latest");
|
||||
} else if (localCodexVersion !== CODEX_VERSION) {
|
||||
logger.warn(`Bundled Codex ${CODEX_VERSION} does not match local Codex ${localCodexVersion}. Keep both current with: npm install -g @openai/codex@latest && npx -y @basketikun/canvas-agent@latest`);
|
||||
}
|
||||
void checkLatestVersions(localCodexVersion);
|
||||
}
|
||||
|
||||
/** 查询 npm,提醒升级不再维护的旧版本。 */
|
||||
async function checkLatestVersions(localCodexVersion: string) {
|
||||
try {
|
||||
const [latestAgent, latestCodex] = await Promise.all([
|
||||
npmVersion("@basketikun/canvas-agent"),
|
||||
npmVersion("@openai/codex"),
|
||||
]);
|
||||
if (isOlder(VERSION, latestAgent)) logger.warn(`Update available: Canvas Agent ${VERSION} -> ${latestAgent}. Run: npx -y @basketikun/canvas-agent@latest`);
|
||||
if (isOlder(CODEX_VERSION, latestCodex)) logger.warn(`Update available: bundled Codex ${CODEX_VERSION} -> ${latestCodex}. Upgrade Canvas Agent with: npx -y @basketikun/canvas-agent@latest`);
|
||||
if (localCodexVersion && isOlder(localCodexVersion, latestCodex)) logger.warn(`Update available: local Codex ${localCodexVersion} -> ${latestCodex}. Run: npm install -g @openai/codex@latest`);
|
||||
} catch {
|
||||
logger.warn("Unable to check the latest npm versions; startup will continue.");
|
||||
}
|
||||
}
|
||||
|
||||
/** 读取本机命令输出中的语义版本号。 */
|
||||
function commandVersion(command: string) {
|
||||
try {
|
||||
return execFileSync(command, ["--version"], { encoding: "utf8", timeout: 5_000 }).match(/\d+\.\d+\.\d+/)?.[0] || "";
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/** 读取 npm 包的最新版本。 */
|
||||
async function npmVersion(name: string) {
|
||||
const command = process.platform === "win32" ? "npm.cmd" : "npm";
|
||||
const { stdout } = await execFileAsync(command, ["view", name, "version"], { encoding: "utf8", timeout: 10_000 });
|
||||
return stdout.trim();
|
||||
}
|
||||
|
||||
/** 比较仅包含数字段的稳定版语义版本。 */
|
||||
function isOlder(current: string, latest: string) {
|
||||
const left = current.split(".").map(Number);
|
||||
const right = latest.split(".").map(Number);
|
||||
for (let index = 0; index < Math.max(left.length, right.length); index++) {
|
||||
if ((left[index] || 0) !== (right[index] || 0)) return (left[index] || 0) < (right[index] || 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user