agent-canvas 0.1.18 → 0.1.19

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.
@@ -2,7 +2,7 @@ import { parseArgs } from "util";
2
2
  import { existsSync, statSync } from "fs";
3
3
  import { resolve, basename } from "path";
4
4
  import { BASE_URL } from "../config.ts";
5
- import { ensureDaemon } from "../daemon-lifecycle.ts";
5
+ import { ensureDaemon, stopDaemon, startDaemon } from "../daemon-lifecycle.ts";
6
6
  import { getSessionId, openBrowser } from "../helpers.ts";
7
7
 
8
8
  export async function handlePush(args: string[]) {
@@ -52,7 +52,26 @@ export async function handlePush(args: string[]) {
52
52
  }),
53
53
  });
54
54
 
55
- const result = await response.json() as any;
55
+ let result = await response.json() as any;
56
+
57
+ // Bun.build() can corrupt internal state in long-running daemons,
58
+ // returning "Unknown Error, TODO". Restart daemon and retry once.
59
+ if (!result.ok && typeof result.error === "string" && result.error.includes("Unknown Error")) {
60
+ console.error("Daemon bundler crashed, restarting...");
61
+ stopDaemon();
62
+ await startDaemon();
63
+ const retryResponse = await fetch(`${BASE_URL}/api/session/${sessionId}/plan`, {
64
+ method: "POST",
65
+ headers: { "Content-Type": "application/json" },
66
+ body: JSON.stringify({
67
+ directory: resolvedPath,
68
+ projectRoot,
69
+ label: label || autoLabel,
70
+ ...(values.response ? { response: values.response } : {}),
71
+ }),
72
+ });
73
+ result = await retryResponse.json() as any;
74
+ }
56
75
 
57
76
  if (!result.ok) {
58
77
  if (result.unconsumedFeedback) {
package/bin/lib/config.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { resolve, join, dirname } from "path";
2
+ import { readFileSync } from "fs";
2
3
  import { tmpdir } from "os";
3
4
 
4
5
  export const PACKAGE_ROOT = resolve(join(dirname(import.meta.path), "../.."));
6
+ export const VERSION = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf-8")).version as string;
5
7
  export const TEMP_DIR = join(tmpdir(), "agent-canvas");
6
8
  export const DAEMON_PORT = parseInt(process.env.CANVAS_PORT || "19400", 10);
7
9
  export const BASE_URL = `http://localhost:${DAEMON_PORT}`;
@@ -1,7 +1,7 @@
1
1
  import { spawn } from "child_process";
2
2
  import { openSync, closeSync, readFileSync, existsSync, mkdirSync, unlinkSync } from "fs";
3
3
  import { join } from "path";
4
- import { PACKAGE_ROOT, TEMP_DIR, BASE_URL, PID_FILE } from "./config.ts";
4
+ import { PACKAGE_ROOT, TEMP_DIR, BASE_URL, PID_FILE, VERSION } from "./config.ts";
5
5
 
6
6
  export async function isDaemonRunning(): Promise<boolean> {
7
7
  try {
@@ -13,6 +13,16 @@ export async function isDaemonRunning(): Promise<boolean> {
13
13
  }
14
14
  }
15
15
 
16
+ async function getDaemonVersion(): Promise<string | null> {
17
+ try {
18
+ const res = await fetch(`${BASE_URL}/health`, { signal: AbortSignal.timeout(1000) });
19
+ const data = await res.json() as any;
20
+ return data.version || null;
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
16
26
  export async function startDaemon(): Promise<void> {
17
27
  console.error("Starting canvas daemon...");
18
28
  mkdirSync(TEMP_DIR, { recursive: true });
@@ -50,7 +60,15 @@ export async function startDaemon(): Promise<void> {
50
60
  }
51
61
 
52
62
  export async function ensureDaemon(): Promise<void> {
53
- if (await isDaemonRunning()) return;
63
+ if (await isDaemonRunning()) {
64
+ const daemonVersion = await getDaemonVersion();
65
+ if (daemonVersion && daemonVersion !== VERSION) {
66
+ console.error(`Daemon version mismatch (daemon: ${daemonVersion}, CLI: ${VERSION}), restarting...`);
67
+ stopDaemon();
68
+ await startDaemon();
69
+ }
70
+ return;
71
+ }
54
72
  await startDaemon();
55
73
  }
56
74
 
@@ -32,6 +32,10 @@ Use the **Write** tool to create `.jsx` files in `.claude/agent-canvas/<session-
32
32
 
33
33
  Components are auto-available — no imports needed. The file can be a JSX fragment (just tags) or a full module with `export default`.
34
34
 
35
+ **Content is JSX, not markdown.** Children of components are plain JSX text and nested components — NOT markdown. If you want to render markdown (lists, bold, headings, etc.), use the `<Markdown>` component explicitly. Raw `**bold**` or `- list items` in JSX children will render as literal text.
36
+
37
+ **JSX escaping rules.** Content is in a JSX context. You MUST escape: `<` as `{'<'}` or `&lt;`, `>` as `{'>'}` or `&gt;`, `{` as `{'{'}` and `}` as `{'}'}`. This is critical when writing code snippets, type signatures like `Option<String>`, or any text containing angle brackets. Alternatively, put such content inside `<CodeBlock>` or `<Markdown>` which handle escaping for you.
38
+
35
39
  ### 2. Push
36
40
 
37
41
  Push the directory to open all canvases in the browser:
@@ -123,7 +127,13 @@ All components are globally available in canvas JSX — no imports needed. For i
123
127
  - **useFeedback** — hook for custom components to contribute computed data to feedback
124
128
  - **Custom components** — use `export default function Canvas()` module format to define helpers
125
129
 
126
- Run `bunx agent-canvas instructions <topic>` for detailed docs on any component or flow.
130
+ **Before using any component or flow, you MUST read its detailed instructions** by running:
131
+
132
+ ```bash
133
+ bunx agent-canvas instructions <topic>
134
+ ```
135
+
136
+ For example: `bunx agent-canvas instructions component-item`, `bunx agent-canvas instructions flow-feature`. Run `bunx agent-canvas instructions --list` to see all available topics.
127
137
 
128
138
  ## Choosing a Flow
129
139
 
@@ -147,10 +147,10 @@ export async function compilePlan(jsx: string, projectRoot?: string): Promise<Co
147
147
  return { ok: false, error: `Runtime error: ${validation.error}` };
148
148
  }
149
149
  return { ok: true, js };
150
- } catch {
150
+ } catch (buildError: any) {
151
151
  // Bun.build() can throw "Unknown Error, TODO" in long-running processes
152
- // when its internal bundler state gets corrupted. Fall back to subprocess.
153
- return await compilePlanSubprocess(tmpFile);
152
+ // when its internal bundler state gets corrupted. Let the CLI handle restart.
153
+ return { ok: false, error: buildError?.message || "Unknown Error" };
154
154
  }
155
155
  } catch (e: any) {
156
156
  return { ok: false, error: e.message };
@@ -159,32 +159,6 @@ export async function compilePlan(jsx: string, projectRoot?: string): Promise<Co
159
159
  }
160
160
  }
161
161
 
162
- async function compilePlanSubprocess(tmpFile: string): Promise<CompileResult> {
163
- const outFile = `${tmpFile}.out.js`;
164
- const proc = Bun.spawn(
165
- ["bun", "build", tmpFile, "--format=esm",
166
- "--external=react", "--external=react-dom",
167
- "--external=#canvas/components", "--external=#canvas/runtime",
168
- "--outfile", outFile],
169
- { stdout: "pipe", stderr: "pipe" },
170
- );
171
- const exitCode = await proc.exited;
172
- if (exitCode !== 0) {
173
- const stderr = await new Response(proc.stderr).text();
174
- return { ok: false, error: stderr || "Compilation failed" };
175
- }
176
- try {
177
- const js = readFileSync(outFile, "utf-8");
178
- const validation = validateCompiledPlan(js);
179
- if (!validation.ok) {
180
- return { ok: false, error: `Runtime error: ${validation.error}` };
181
- }
182
- return { ok: true, js };
183
- } finally {
184
- try { unlinkSync(outFile); } catch {}
185
- }
186
- }
187
-
188
162
  /**
189
163
  * Find <Markdown file="..." /> tags and inject __content with the file contents
190
164
  * resolved at compile time.
@@ -10,6 +10,7 @@ export interface ApiContext {
10
10
  broadcastPlanUpdate: (id: string) => void;
11
11
  broadcastRevisionUpdate: (id: string) => void;
12
12
  port: number;
13
+ version: string;
13
14
  }
14
15
 
15
16
  export function createApiHandlers(ctx: ApiContext): Route[] {
@@ -132,7 +133,7 @@ export function createApiHandlers(ctx: ApiContext): Route[] {
132
133
 
133
134
  function handleHealth(): Response {
134
135
  const sessions = sessionManager.list().map((s) => s.id);
135
- return jsonResponse({ ok: true, sessions });
136
+ return jsonResponse({ ok: true, sessions, version: ctx.version });
136
137
  }
137
138
 
138
139
  function handleMeta(_req: Request, _url: URL, match: URLPatternResult): Response {
@@ -1,5 +1,5 @@
1
- import { writeFileSync, mkdirSync } from "fs";
2
- import { join } from "path";
1
+ import { writeFileSync, mkdirSync, readFileSync } from "fs";
2
+ import { join, dirname } from "path";
3
3
  import { tmpdir } from "os";
4
4
  import { SessionManager } from "./session";
5
5
  import { createWebSocketManager, type WSData } from "./websocket";
@@ -11,6 +11,7 @@ import { createStaticHandlers } from "./handlers/static";
11
11
  import { createUploadHandlers } from "./handlers/uploads";
12
12
 
13
13
  const PORT = parseInt(process.env.CANVAS_PORT || "19400", 10);
14
+ const VERSION = JSON.parse(readFileSync(join(dirname(import.meta.dir), "..", "package.json"), "utf-8")).version as string;
14
15
 
15
16
  // Write PID file so CLI can find and stop us
16
17
  const pidDir = join(tmpdir(), "agent-canvas");
@@ -21,7 +22,7 @@ const sessionManager = new SessionManager();
21
22
  const wsManager = createWebSocketManager(sessionManager);
22
23
 
23
24
  const routes = [
24
- ...createApiHandlers({ sessionManager, broadcastPlanUpdate: wsManager.broadcastPlanUpdate, broadcastRevisionUpdate: wsManager.broadcastRevisionUpdate, port: PORT }),
25
+ ...createApiHandlers({ sessionManager, broadcastPlanUpdate: wsManager.broadcastPlanUpdate, broadcastRevisionUpdate: wsManager.broadcastRevisionUpdate, port: PORT, version: VERSION }),
25
26
  ...createFileHandlers(sessionManager),
26
27
  ...createUploadHandlers(sessionManager),
27
28
  ...createStaticHandlers(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-canvas",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Interactive visual canvas for Claude Code — rich annotatable documents in the browser",
5
5
  "bin": {
6
6
  "agent-canvas": "bin/agent-canvas.js"