agent-canvas 0.1.9 → 0.1.10
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.
- package/.github/dark.png +0 -0
- package/.github/light.png +0 -0
- package/README.md +10 -0
- package/daemon/client/AnnotationEditor.tsx +307 -0
- package/daemon/client/AnnotationProvider.tsx +15 -3
- package/daemon/client/AnnotationSidebar.tsx +54 -28
- package/daemon/client/App.tsx +25 -14
- package/daemon/client/CompareView.tsx +53 -22
- package/daemon/client/FileBrowser.tsx +2 -1
- package/daemon/client/FileViewer.tsx +41 -161
- package/daemon/client/PlanRenderer.tsx +30 -147
- package/daemon/client/Popover.tsx +168 -108
- package/daemon/client/ResponsePreview.tsx +7 -1
- package/daemon/client/RevisionSelect.tsx +8 -1
- package/daemon/client/annotationDom.ts +104 -0
- package/daemon/client/components/Mermaid.tsx +2 -2
- package/daemon/client/generateMarkdown.ts +8 -0
- package/daemon/client/highlightRange.ts +1 -22
- package/daemon/client/runtime.ts +4 -1
- package/daemon/client/styles.css +28 -2
- package/daemon/client/theme.css +1 -1
- package/daemon/client/useTextAnnotation.tsx +183 -0
- package/daemon/dist/client.css +1 -1
- package/daemon/dist/client.js +61 -85
- package/daemon/dist/components.js +54 -54
- package/daemon/dist/index.html +1 -1
- package/daemon/src/handlers/uploads.ts +62 -0
- package/daemon/src/paths.ts +4 -0
- package/daemon/src/server.ts +2 -0
- package/daemon/src/session.ts +39 -1
- package/package.json +1 -1
- package/hooks/session-start.sh +0 -13
package/daemon/dist/index.html
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
--color-highlight-bg: rgba(255, 220, 100, 0.15);
|
|
43
43
|
--color-highlight-border: rgba(255, 220, 100, 0.35);
|
|
44
44
|
--color-highlight-annotation: rgba(255, 220, 100, 0.18);
|
|
45
|
-
--color-highlight-active: rgba(255, 220, 100, 0.
|
|
45
|
+
--color-highlight-active: rgba(255, 220, 100, 0.35);
|
|
46
46
|
--color-highlight-selected: rgba(255, 220, 100, 0.10);
|
|
47
47
|
|
|
48
48
|
--color-badge-bg: rgba(196, 154, 58, 0.20);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { writeFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { UPLOADS_DIR } from "../paths";
|
|
4
|
+
import type { SessionManager } from "../session";
|
|
5
|
+
import { jsonResponse } from "./utils";
|
|
6
|
+
import type { Route } from "../router";
|
|
7
|
+
|
|
8
|
+
const IMAGE_MIME: Record<string, string> = {
|
|
9
|
+
png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg",
|
|
10
|
+
gif: "image/gif", svg: "image/svg+xml", webp: "image/webp",
|
|
11
|
+
bmp: "image/bmp", avif: "image/avif",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function createUploadHandlers(sessionManager: SessionManager): Route[] {
|
|
15
|
+
async function handleUpload(req: Request, _url: URL, match: URLPatternResult): Promise<Response> {
|
|
16
|
+
const sessionId = match.pathname.groups.id!;
|
|
17
|
+
if (!sessionManager.get(sessionId)) {
|
|
18
|
+
return jsonResponse({ error: "Session not found" }, 404);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const formData = await req.formData();
|
|
22
|
+
const file = formData.get("image") as File | null;
|
|
23
|
+
if (!file) return jsonResponse({ error: "No image provided" }, 400);
|
|
24
|
+
if (!file.type.startsWith("image/")) return jsonResponse({ error: "Not an image" }, 400);
|
|
25
|
+
if (file.size > 10 * 1024 * 1024) return jsonResponse({ error: "Image too large (max 10MB)" }, 413);
|
|
26
|
+
|
|
27
|
+
const ext = file.name.split(".").pop()?.toLowerCase() || "png";
|
|
28
|
+
const filename = `${crypto.randomUUID()}.${ext}`;
|
|
29
|
+
const filepath = join(UPLOADS_DIR, filename);
|
|
30
|
+
|
|
31
|
+
const buffer = await file.arrayBuffer();
|
|
32
|
+
writeFileSync(filepath, Buffer.from(buffer));
|
|
33
|
+
|
|
34
|
+
return jsonResponse({ path: filepath, filename });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function handleUploadServe(_req: Request, _url: URL, match: URLPatternResult): Promise<Response> {
|
|
38
|
+
const filename = match.pathname.groups.filename!;
|
|
39
|
+
if (filename.includes("/") || filename.includes("..")) {
|
|
40
|
+
return jsonResponse({ error: "Invalid filename" }, 400);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const filepath = join(UPLOADS_DIR, filename);
|
|
44
|
+
const file = Bun.file(filepath);
|
|
45
|
+
if (!(await file.exists())) return jsonResponse({ error: "Not found" }, 404);
|
|
46
|
+
|
|
47
|
+
const ext = filename.split(".").pop()?.toLowerCase() || "";
|
|
48
|
+
const mime = IMAGE_MIME[ext] || "application/octet-stream";
|
|
49
|
+
|
|
50
|
+
return new Response(file, {
|
|
51
|
+
headers: {
|
|
52
|
+
"Content-Type": mime,
|
|
53
|
+
"Cache-Control": "public, max-age=3600",
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return [
|
|
59
|
+
{ method: "POST", pattern: new URLPattern({ pathname: "/api/session/:id/upload" }), handler: handleUpload },
|
|
60
|
+
{ method: "GET", pattern: new URLPattern({ pathname: "/api/uploads/:filename" }), handler: handleUploadServe },
|
|
61
|
+
];
|
|
62
|
+
}
|
package/daemon/src/paths.ts
CHANGED
|
@@ -17,6 +17,10 @@ export const PID_FILE = join(TEMP_DIR, "daemon.pid");
|
|
|
17
17
|
/** Temp dir for JSX compilation */
|
|
18
18
|
export const COMPILE_TEMP_DIR = join(TEMP_DIR, "compile");
|
|
19
19
|
|
|
20
|
+
/** Uploaded annotation images */
|
|
21
|
+
export const UPLOADS_DIR = join(TEMP_DIR, "uploads");
|
|
22
|
+
|
|
20
23
|
// Ensure dirs exist
|
|
21
24
|
mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
22
25
|
mkdirSync(COMPILE_TEMP_DIR, { recursive: true });
|
|
26
|
+
mkdirSync(UPLOADS_DIR, { recursive: true });
|
package/daemon/src/server.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { jsonResponse, corsHeaders } from "./handlers/utils";
|
|
|
8
8
|
import { createApiHandlers } from "./handlers/api";
|
|
9
9
|
import { createFileHandlers } from "./handlers/files";
|
|
10
10
|
import { createStaticHandlers } from "./handlers/static";
|
|
11
|
+
import { createUploadHandlers } from "./handlers/uploads";
|
|
11
12
|
|
|
12
13
|
const PORT = parseInt(process.env.CANVAS_PORT || "19400", 10);
|
|
13
14
|
|
|
@@ -22,6 +23,7 @@ const wsManager = createWebSocketManager(sessionManager);
|
|
|
22
23
|
const routes = [
|
|
23
24
|
...createApiHandlers({ sessionManager, broadcastPlanUpdate: wsManager.broadcastPlanUpdate, port: PORT }),
|
|
24
25
|
...createFileHandlers(sessionManager),
|
|
26
|
+
...createUploadHandlers(sessionManager),
|
|
25
27
|
...createStaticHandlers(),
|
|
26
28
|
];
|
|
27
29
|
|
package/daemon/src/session.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { mkdirSync, writeFileSync, readFileSync, existsSync, readdirSync, rmSync
|
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import { SESSIONS_DIR } from "./paths";
|
|
4
4
|
|
|
5
|
+
export interface DiffStats {
|
|
6
|
+
added: number;
|
|
7
|
+
removed: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
export interface RevisionInfo {
|
|
6
11
|
revision: number;
|
|
7
12
|
label?: string;
|
|
@@ -10,6 +15,7 @@ export interface RevisionInfo {
|
|
|
10
15
|
hasFeedback: boolean;
|
|
11
16
|
feedbackConsumed: boolean;
|
|
12
17
|
response?: string;
|
|
18
|
+
diffStats?: DiffStats;
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
export interface SessionData {
|
|
@@ -40,6 +46,27 @@ interface LegacyMeta {
|
|
|
40
46
|
|
|
41
47
|
const STALE_TIMEOUT_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
42
48
|
|
|
49
|
+
function computeLineDiffStats(oldText: string, newText: string): DiffStats {
|
|
50
|
+
const oldLines = oldText.split("\n");
|
|
51
|
+
const newLines = newText.split("\n");
|
|
52
|
+
const m = oldLines.length, n = newLines.length;
|
|
53
|
+
|
|
54
|
+
// LCS length via two-row DP (O(n) space)
|
|
55
|
+
let prev = new Uint32Array(n + 1);
|
|
56
|
+
let curr = new Uint32Array(n + 1);
|
|
57
|
+
for (let i = 1; i <= m; i++) {
|
|
58
|
+
for (let j = 1; j <= n; j++) {
|
|
59
|
+
curr[j] = oldLines[i - 1] === newLines[j - 1]
|
|
60
|
+
? prev[j - 1] + 1
|
|
61
|
+
: Math.max(prev[j], curr[j - 1]);
|
|
62
|
+
}
|
|
63
|
+
[prev, curr] = [curr, prev];
|
|
64
|
+
curr.fill(0);
|
|
65
|
+
}
|
|
66
|
+
const lcsLen = prev[n];
|
|
67
|
+
return { added: n - lcsLen, removed: m - lcsLen };
|
|
68
|
+
}
|
|
69
|
+
|
|
43
70
|
export class SessionManager {
|
|
44
71
|
private sessions = new Map<string, SessionData>();
|
|
45
72
|
|
|
@@ -162,7 +189,18 @@ export class SessionManager {
|
|
|
162
189
|
const now = new Date().toISOString();
|
|
163
190
|
const revision = existing ? existing.currentRevision + 1 : 1;
|
|
164
191
|
|
|
165
|
-
|
|
192
|
+
let diffStats: DiffStats | undefined;
|
|
193
|
+
if (existing && sourceFile) {
|
|
194
|
+
const prev = [...existing.revisions]
|
|
195
|
+
.reverse()
|
|
196
|
+
.find((r) => r.sourceFile === sourceFile);
|
|
197
|
+
if (prev) {
|
|
198
|
+
const prevJsx = this.readRevisionJsx(id, prev.revision);
|
|
199
|
+
if (prevJsx) diffStats = computeLineDiffStats(prevJsx, jsx);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const revInfo: RevisionInfo = { revision, createdAt: now, hasFeedback: false, feedbackConsumed: false, ...(label ? { label } : {}), ...(sourceFile ? { sourceFile } : {}), ...(response ? { response } : {}), ...(diffStats ? { diffStats } : {}) };
|
|
166
204
|
const revisions = existing ? [...existing.revisions, revInfo] : [revInfo];
|
|
167
205
|
|
|
168
206
|
const session: SessionData = {
|
package/package.json
CHANGED
package/hooks/session-start.sh
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Injects CANVAS_SESSION_ID and CANVAS_PROJECT_ROOT into Claude Code environment.
|
|
3
|
-
# Called by Claude Code's SessionStart hook.
|
|
4
|
-
|
|
5
|
-
if [ -n "$CLAUDE_ENV_FILE" ]; then
|
|
6
|
-
# Extract session ID from CLAUDE_ENV_FILE path:
|
|
7
|
-
# /home/.../.claude/session-env/<SESSION_ID>/sessionstart-hook-0.sh
|
|
8
|
-
SESSION_ID=$(basename "$(dirname "$CLAUDE_ENV_FILE")")
|
|
9
|
-
CWD="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
10
|
-
|
|
11
|
-
echo "export CANVAS_SESSION_ID=$SESSION_ID" >> "$CLAUDE_ENV_FILE"
|
|
12
|
-
echo "export CANVAS_PROJECT_ROOT=$CWD" >> "$CLAUDE_ENV_FILE"
|
|
13
|
-
fi
|