agentic-canvas 0.5.0 → 0.6.0
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/README.md +3 -3
- package/dist/{chunk-V74PWJKY.js → chunk-BIIC2DPZ.js} +38 -38
- package/dist/{chunk-VHHJIBAB.js → chunk-SIDWRC3P.js} +7 -6
- package/dist/cli.js +1 -1
- package/dist/embed.js +2 -2
- package/dist/index.js +1 -1
- package/dist/live-worker.js +1 -1
- package/dist/live.js +9 -6
- package/dist/worker.d.ts +4 -4
- package/dist/worker.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
A shared whiteboard for diagrams, drawn by people and agents.
|
|
9
9
|
|
|
10
|
-
> Status: 0.
|
|
10
|
+
> Status: 0.6.0. The board model, the agent operations, the exports, the CLI, the
|
|
11
11
|
> MCP server, a small Worker API, and live co-editing work. The CLI, the MCP server,
|
|
12
12
|
> and the SDK can join a live room. A remote client calls the Worker API. A small web UI, served by the Worker, is new in 0.4.0.
|
|
13
13
|
> The package exports `agentic-canvas/ui`, a board view you can mount in your own app, new in 0.5.0.
|
|
@@ -327,9 +327,9 @@ The stylesheet is a separate export, `agentic-canvas/ui.css`. Load `agentic-desi
|
|
|
327
327
|
|---|---|
|
|
328
328
|
| `createApp(options?)` | Make the Hono app for the Worker. Pass `live` to turn on the live-room socket. |
|
|
329
329
|
| `default` | The Worker, without live rooms. |
|
|
330
|
-
| `authenticate(request, env)` | Read a bearer token from the `Authorization` header, or from a WebSocket sub-protocol. Return the actor and the role, or `null`. |
|
|
330
|
+
| `authenticate(request, env)` | Read a bearer token from the `Authorization` header, or from a WebSocket sub-protocol. Return a promise of the actor and the role, or `null`. |
|
|
331
331
|
| `sniffImage(bytes)` | Find the image type from the first bytes, or return `null`. The client's claim of a type is not trusted. |
|
|
332
|
-
| `WS_PROTOCOL` | The sub-protocol name
|
|
332
|
+
| `WS_PROTOCOL` | The sub-protocol name for a WebSocket: `agentic-canvas`. The token travels in the entry `agentic-canvas.bearer.<token in base64url>`. |
|
|
333
333
|
| `WorkerEnv` (type), `AppOptions` (type), `Role` (type) | Types for the Worker bindings, the app options and the role, `editor` or `viewer`. |
|
|
334
334
|
|
|
335
335
|
### `agentic-canvas/live-worker`
|
|
@@ -5,13 +5,11 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
CanvasError
|
|
7
7
|
} from "./chunk-JCYNPYYJ.js";
|
|
8
|
-
import {
|
|
9
|
-
principalSchema
|
|
10
|
-
} from "./chunk-SPP6PINW.js";
|
|
11
8
|
|
|
12
9
|
// src/worker.ts
|
|
13
|
-
import { parseRegion } from "agentic-identity";
|
|
10
|
+
import { hasScope, parsePrincipal, parseRegion } from "agentic-identity";
|
|
14
11
|
import { createD1Store, createR2Blobs, D1_SCHEMA } from "agentic-identity/cloudflare";
|
|
12
|
+
import { acceptWebSocketProtocol, principalFromWebSocketRequest } from "agentic-identity/http";
|
|
15
13
|
|
|
16
14
|
// node_modules/hono/dist/request/constants.js
|
|
17
15
|
var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
|
|
@@ -1950,36 +1948,41 @@ function sameText(a, b) {
|
|
|
1950
1948
|
for (let i = 0; i < Math.max(x.length, y.length); i++) diff |= (x[i] ?? 0) ^ (y[i] ?? 0);
|
|
1951
1949
|
return diff === 0;
|
|
1952
1950
|
}
|
|
1953
|
-
var WS_PROTOCOL = "agentic-canvas
|
|
1954
|
-
function
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1951
|
+
var WS_PROTOCOL = "agentic-canvas";
|
|
1952
|
+
function tokenAuth(env) {
|
|
1953
|
+
return {
|
|
1954
|
+
async authenticate(request) {
|
|
1955
|
+
const token = /^Bearer (.+)$/.exec(request.headers.get("authorization") ?? "")?.[1];
|
|
1956
|
+
if (!token || !env.CANVAS_API_TOKENS) return null;
|
|
1957
|
+
let grants;
|
|
1958
|
+
try {
|
|
1959
|
+
grants = JSON.parse(env.CANVAS_API_TOKENS);
|
|
1960
|
+
} catch {
|
|
1961
|
+
return null;
|
|
1962
|
+
}
|
|
1963
|
+
if (typeof grants !== "object" || grants === null || Array.isArray(grants)) return null;
|
|
1964
|
+
let found;
|
|
1965
|
+
for (const [candidate, grant] of Object.entries(grants)) {
|
|
1966
|
+
if (sameText(candidate, token) && candidate.length >= MIN_TOKEN_LENGTH) found = grant;
|
|
1967
|
+
}
|
|
1968
|
+
if (typeof found !== "object" || found === null) return null;
|
|
1969
|
+
const { actor, kind, role = "editor" } = found;
|
|
1970
|
+
let step;
|
|
1971
|
+
try {
|
|
1972
|
+
step = parsePrincipal({ kind, id: actor });
|
|
1973
|
+
} catch {
|
|
1974
|
+
return null;
|
|
1975
|
+
}
|
|
1976
|
+
if (step.kind === SEEDER.kind && step.id === SEEDER.id) return null;
|
|
1977
|
+
if (role !== "editor" && role !== "viewer") return null;
|
|
1978
|
+
return { principalId: step.id, kind: step.kind, chain: [step], scopes: role === "viewer" ? ["canvas:read"] : ["canvas:read", "canvas:write"] };
|
|
1979
|
+
}
|
|
1980
|
+
};
|
|
1963
1981
|
}
|
|
1964
|
-
function authenticate(request, env) {
|
|
1965
|
-
const
|
|
1966
|
-
if (
|
|
1967
|
-
|
|
1968
|
-
try {
|
|
1969
|
-
grants = JSON.parse(env.CANVAS_API_TOKENS);
|
|
1970
|
-
} catch {
|
|
1971
|
-
return null;
|
|
1972
|
-
}
|
|
1973
|
-
if (typeof grants !== "object" || grants === null || Array.isArray(grants)) return null;
|
|
1974
|
-
let found;
|
|
1975
|
-
for (const [candidate, grant] of Object.entries(grants)) {
|
|
1976
|
-
if (sameText(candidate, token) && candidate.length >= MIN_TOKEN_LENGTH) found = grant;
|
|
1977
|
-
}
|
|
1978
|
-
if (typeof found !== "object" || found === null) return null;
|
|
1979
|
-
const { actor, kind, role = "editor" } = found;
|
|
1980
|
-
const parsed = principalSchema.safeParse({ kind, id: actor });
|
|
1981
|
-
const taken = parsed.success && parsed.data.kind === SEEDER.kind && parsed.data.id === SEEDER.id;
|
|
1982
|
-
return parsed.success && !taken && (role === "editor" || role === "viewer") ? { actor: parsed.data, role } : null;
|
|
1982
|
+
async function authenticate(request, env) {
|
|
1983
|
+
const found = await principalFromWebSocketRequest(request, tokenAuth(env), { protocol: WS_PROTOCOL });
|
|
1984
|
+
if (found instanceof Response) return null;
|
|
1985
|
+
return { actor: { kind: found.kind, id: found.principalId }, role: hasScope(found.scopes, "canvas:write") ? "editor" : "viewer" };
|
|
1983
1986
|
}
|
|
1984
1987
|
function sniffImage(bytes) {
|
|
1985
1988
|
const at = (offset, ...sig) => sig.every((b, i) => bytes[offset + i] === b);
|
|
@@ -2058,7 +2061,7 @@ function createApp(options = {}) {
|
|
|
2058
2061
|
return fail("NOT_CONFIGURED", 'set CANVAS_REGION to "eu", "us", or "unknown"', 503);
|
|
2059
2062
|
}
|
|
2060
2063
|
if (!c.env.DB || !c.env.IMAGES) return fail("NOT_CONFIGURED", "bind DB (D1) and IMAGES (R2)", 503);
|
|
2061
|
-
const who = authenticate(c.req.raw, c.env);
|
|
2064
|
+
const who = await authenticate(c.req.raw, c.env);
|
|
2062
2065
|
if (!who) return fail("UNAUTHORIZED", "send a valid bearer token", 401);
|
|
2063
2066
|
if (who.role === "viewer" && c.req.method !== "GET") return fail("FORBIDDEN", "this token is a viewer; it cannot change a board", 403);
|
|
2064
2067
|
c.set("actor", who.actor);
|
|
@@ -2094,10 +2097,7 @@ function createApp(options = {}) {
|
|
|
2094
2097
|
await saveRoom(c, true);
|
|
2095
2098
|
const { actor, role } = c.var;
|
|
2096
2099
|
const opened = await options.live(c.env).connect(id, c.req.raw, { principal: { id: actor.id, kind: actor.kind }, role, name: actor.id });
|
|
2097
|
-
|
|
2098
|
-
const headers = new Headers(opened.headers);
|
|
2099
|
-
headers.set("sec-websocket-protocol", WS_PROTOCOL);
|
|
2100
|
-
return new Response(null, { status: 101, headers, webSocket: opened.webSocket });
|
|
2100
|
+
return acceptWebSocketProtocol(c.req.raw, opened, WS_PROTOCOL);
|
|
2101
2101
|
});
|
|
2102
2102
|
api.get("/boards/:id/state", async (c) => !options.live ? fail("NOT_FOUND", "this server hosts no live rooms", 404) : c.body(new Uint8Array(await c.var.service.state(c.req.param("id"))), 200, { "content-type": "application/octet-stream" }));
|
|
2103
2103
|
api.post("/boards/:id/checkpoint", async (c) => {
|
|
@@ -3,13 +3,12 @@ import {
|
|
|
3
3
|
SHAPE_TYPES
|
|
4
4
|
} from "./chunk-JCYNPYYJ.js";
|
|
5
5
|
import {
|
|
6
|
-
PRINCIPAL_KINDS
|
|
7
|
-
principalSchema
|
|
6
|
+
PRINCIPAL_KINDS
|
|
8
7
|
} from "./chunk-SPP6PINW.js";
|
|
9
8
|
|
|
10
9
|
// src/version.ts
|
|
11
10
|
import { createRequire } from "module";
|
|
12
|
-
var VERSION = true ? "0.
|
|
11
|
+
var VERSION = true ? "0.6.0" : readPackageVersion();
|
|
13
12
|
|
|
14
13
|
// src/brand.ts
|
|
15
14
|
var BRAND = {
|
|
@@ -51,17 +50,19 @@ async function svgToPng(svg) {
|
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
// src/actor.ts
|
|
53
|
+
import { parsePrincipal } from "agentic-identity";
|
|
54
54
|
var ActorConfigError = class extends Error {
|
|
55
55
|
};
|
|
56
56
|
function operatorActor(env = process.env) {
|
|
57
57
|
const raw = env.CANVAS_ACTOR;
|
|
58
58
|
if (!raw) throw new ActorConfigError("the operator must set CANVAS_ACTOR=<kind>:<id>");
|
|
59
59
|
const at = raw.indexOf(":");
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
try {
|
|
61
|
+
if (at < 0) throw new Error("no kind");
|
|
62
|
+
return parsePrincipal({ kind: raw.slice(0, at), id: raw.slice(at + 1) });
|
|
63
|
+
} catch {
|
|
62
64
|
throw new ActorConfigError(`bad CANVAS_ACTOR "${raw}": use <kind>:<id>, kind is one of ${PRINCIPAL_KINDS.join(", ")}`);
|
|
63
65
|
}
|
|
64
|
-
return parsed.data;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
// src/mcp.ts
|
package/dist/cli.js
CHANGED
package/dist/embed.js
CHANGED
|
@@ -228,11 +228,11 @@ function Failure({ message, retry }) {
|
|
|
228
228
|
|
|
229
229
|
// ui/presence.ts
|
|
230
230
|
import { SyncClient } from "agentic-realtime/client";
|
|
231
|
-
var WS_PROTOCOL = "agentic-canvas
|
|
231
|
+
var WS_PROTOCOL = "agentic-canvas";
|
|
232
232
|
var b64url = (text) => btoa(String.fromCharCode(...new TextEncoder().encode(text))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
233
233
|
var bearerSocket = (token) => class extends WebSocket {
|
|
234
234
|
constructor(url) {
|
|
235
|
-
super(url, [WS_PROTOCOL, b64url(token)]);
|
|
235
|
+
super(url, [WS_PROTOCOL, `${WS_PROTOCOL}.bearer.${b64url(token)}`]);
|
|
236
236
|
}
|
|
237
237
|
};
|
|
238
238
|
function roomUrl(baseUrl, board) {
|
package/dist/index.js
CHANGED
package/dist/live-worker.js
CHANGED
package/dist/live.js
CHANGED
|
@@ -5,11 +5,10 @@ import {
|
|
|
5
5
|
readBoard,
|
|
6
6
|
toSvg
|
|
7
7
|
} from "./chunk-JCYNPYYJ.js";
|
|
8
|
-
import
|
|
9
|
-
principalSchema
|
|
10
|
-
} from "./chunk-SPP6PINW.js";
|
|
8
|
+
import "./chunk-SPP6PINW.js";
|
|
11
9
|
|
|
12
10
|
// src/live.ts
|
|
11
|
+
import { parsePrincipal } from "agentic-identity";
|
|
13
12
|
import { SyncClient } from "agentic-realtime/client";
|
|
14
13
|
var CODES = ["INVALID", "DUPLICATE_ID", "NOT_FOUND", "BAD_REFERENCE", "LIMIT", "UNAVAILABLE", "FORBIDDEN"];
|
|
15
14
|
var base = (server) => server.replace(/\/$/, "") + "/api/v1/canvas";
|
|
@@ -33,9 +32,13 @@ async function whoAmI(server, token) {
|
|
|
33
32
|
const response = await call(server, token, "/me");
|
|
34
33
|
if (!response.ok) throw await refusal(response, "the server refused the token");
|
|
35
34
|
const { actor, kind, role } = await json(response);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
let principal;
|
|
36
|
+
try {
|
|
37
|
+
principal = parsePrincipal({ kind, id: actor });
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
if (!principal || role !== "editor" && role !== "viewer") throw new CanvasError("INVALID", "the server sent an identity this client does not know");
|
|
41
|
+
return { actor: principal, role };
|
|
39
42
|
}
|
|
40
43
|
var LiveBoard = class _LiveBoard {
|
|
41
44
|
constructor(board, me, client, id, options, refusedAtStart) {
|
package/dist/worker.d.ts
CHANGED
|
@@ -40,13 +40,13 @@ interface AppOptions {
|
|
|
40
40
|
/** Makes the live-room server from the Worker bindings. Without it, the ws route answers 404. */
|
|
41
41
|
live?: (env: WorkerEnv) => Realtime;
|
|
42
42
|
}
|
|
43
|
-
/** A browser cannot set a header on a WebSocket. It offers the
|
|
44
|
-
declare const WS_PROTOCOL = "agentic-canvas
|
|
43
|
+
/** A browser cannot set a header on a WebSocket. It offers the sub-protocols `agentic-canvas` and `agentic-canvas.bearer.<token in base64url>`. */
|
|
44
|
+
declare const WS_PROTOCOL = "agentic-canvas";
|
|
45
45
|
/** The principal and role for a bearer token, or null. The kind is never defaulted: a grant without a valid kind gets null. */
|
|
46
|
-
declare function authenticate(request: Request, env: WorkerEnv): {
|
|
46
|
+
declare function authenticate(request: Request, env: WorkerEnv): Promise<{
|
|
47
47
|
actor: Principal;
|
|
48
48
|
role: Role;
|
|
49
|
-
} | null
|
|
49
|
+
} | null>;
|
|
50
50
|
/** The image types we accept, found by their first bytes. The client's claim of a type is not trusted. */
|
|
51
51
|
declare function sniffImage(bytes: Uint8Array): string | null;
|
|
52
52
|
declare function createApp(options?: AppOptions): Hono<App>;
|
package/dist/worker.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-canvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "A shared whiteboard for diagrams, drawn by people and agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"whiteboard",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"@dagrejs/dagre": "^3.1.3",
|
|
82
82
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
83
83
|
"agentic-design": "^0.2.0",
|
|
84
|
-
"agentic-identity": "^0.
|
|
84
|
+
"agentic-identity": "^0.6.0",
|
|
85
85
|
"agentic-realtime": "^0.3.0",
|
|
86
86
|
"yjs": "^13.6.0",
|
|
87
87
|
"zod": "^3.25.0"
|