@xiaohhhh1/canvas-agent 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,3 @@
1
- /** 插件 MCP 启动时确保本机 HTTP/后台队列服务常驻,无需客户另开终端。 */
1
+ /** Ensure the local HTTP worker is running the same version as the MCP process. */
2
2
  export declare function ensureHttpServer(): Promise<void>;
3
+ export declare function healthVersion(value: unknown): string;
@@ -1,10 +1,21 @@
1
1
  import { spawn } from "node:child_process";
2
- import { loadConfig } from "../config.js";
3
- /** 插件 MCP 启动时确保本机 HTTP/后台队列服务常驻,无需客户另开终端。 */
2
+ import { loadConfig, VERSION } from "../config.js";
3
+ /** Ensure the local HTTP worker is running the same version as the MCP process. */
4
4
  export async function ensureHttpServer() {
5
5
  const config = loadConfig(true);
6
- if (await healthy(config.url))
6
+ const current = await probeHealth(config.url);
7
+ if (current.ok && current.version === VERSION)
7
8
  return;
9
+ if (current.ok) {
10
+ const stopped = await requestShutdown(config.url, config.token);
11
+ if (!stopped)
12
+ throw new Error(`本机 Canvas Agent ${current.version || "旧版本"} 仍在运行,请关闭后重试以升级到 ${VERSION}`);
13
+ for (let attempt = 0; attempt < 40 && (await probeHealth(config.url)).ok; attempt += 1) {
14
+ await new Promise((resolve) => setTimeout(resolve, 100));
15
+ }
16
+ if ((await probeHealth(config.url)).ok)
17
+ throw new Error("旧版 Canvas Agent 未能正常退出,请关闭后重试");
18
+ }
8
19
  const entry = process.argv[1];
9
20
  if (!entry)
10
21
  throw new Error("无法定位 Canvas Agent 启动文件");
@@ -12,14 +23,35 @@ export async function ensureHttpServer() {
12
23
  child.unref();
13
24
  for (let attempt = 0; attempt < 40; attempt += 1) {
14
25
  await new Promise((resolve) => setTimeout(resolve, 250));
15
- if (await healthy(config.url))
26
+ const next = await probeHealth(config.url);
27
+ if (next.ok && next.version === VERSION)
16
28
  return;
17
29
  }
18
30
  throw new Error("本机 Canvas Agent 后台服务启动失败");
19
31
  }
20
- async function healthy(url) {
32
+ export function healthVersion(value) {
33
+ if (!value || typeof value !== "object" || Array.isArray(value))
34
+ return "";
35
+ return typeof value.version === "string" ? String(value.version) : "";
36
+ }
37
+ async function probeHealth(url) {
21
38
  try {
22
39
  const response = await fetch(new URL("/health", url), { signal: AbortSignal.timeout(800) });
40
+ if (!response.ok)
41
+ return { ok: false, version: "" };
42
+ return { ok: true, version: healthVersion(await response.json()) };
43
+ }
44
+ catch {
45
+ return { ok: false, version: "" };
46
+ }
47
+ }
48
+ async function requestShutdown(url, token) {
49
+ try {
50
+ const response = await fetch(new URL("/agent/shutdown", url), {
51
+ method: "POST",
52
+ headers: { "x-canvas-agent-token": token },
53
+ signal: AbortSignal.timeout(1500),
54
+ });
23
55
  return response.ok;
24
56
  }
25
57
  catch {
@@ -5,7 +5,7 @@ import express from "express";
5
5
  import { runClaudeTurn } from "../agent/claude.js";
6
6
  import { archiveCodexThread, interruptCodexTurn, isRecoverableThreadError, listCodexThreads, readCodexThread, resolveCodexApproval, resumeCodexThread, runCodexTurn, startCodexThread, summarizeCodexThread, verifyCodexThreadWorkspace } from "../agent/codex.js";
7
7
  import { CanvasSession } from "../canvas/session.js";
8
- import { DEFAULT_PORT, ensureSiteWorkspace, loadConfig, saveConfig, updateSiteWorkspace } from "../config.js";
8
+ import { DEFAULT_PORT, ensureSiteWorkspace, loadConfig, saveConfig, updateSiteWorkspace, VERSION } from "../config.js";
9
9
  import { startRelayBridge } from "../relay-bridge.js";
10
10
  import { logger } from "../utils/logger.js";
11
11
  import { windowsRootExecutable, windowsSystemExecutable } from "../utils/windows.js";
@@ -53,13 +53,21 @@ export function startHttpServer() {
53
53
  return void res.json({});
54
54
  next();
55
55
  });
56
- app.get("/health", (_req, res) => res.json(session.health()));
56
+ app.get("/health", (_req, res) => res.json({ ...session.health(), version: VERSION }));
57
57
  app.get("/config", (_req, res) => res.json({ ok: true, url: config.url, hasToken: true }));
58
58
  app.use((req, res, next) => {
59
59
  if (validToken(req, requestUrl(req, config), config.token))
60
60
  return next();
61
61
  res.status(401).json({ ok: false, error: "invalid token" });
62
62
  });
63
+ let httpServer;
64
+ app.post("/agent/shutdown", (_req, res) => {
65
+ res.json({ ok: true, version: VERSION });
66
+ setTimeout(() => {
67
+ httpServer?.close(() => process.exit(0));
68
+ setTimeout(() => process.exit(0), 2000).unref();
69
+ }, 50).unref();
70
+ });
63
71
  app.get("/events", (req, res) => session.openEvents(requestUrl(req, config), res));
64
72
  app.post("/canvas/state", (req, res) => {
65
73
  session.updateState(req.body, String(req.query.clientId || "") || undefined);
@@ -259,7 +267,7 @@ export function startHttpServer() {
259
267
  logger.error("HTTP request failed", { method: req.method, path: req.path, error });
260
268
  res.status(500).json({ ok: false, error: error.message });
261
269
  });
262
- app.listen(port, "127.0.0.1", () => {
270
+ httpServer = app.listen(port, "127.0.0.1", () => {
263
271
  console.log("Infinite Canvas Agent");
264
272
  console.log(`Local URL: ${config.url}`);
265
273
  console.log(`Connect token: ${config.token}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiaohhhh1/canvas-agent",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",