agentic-canvas 0.6.1 → 0.8.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 +85 -25
- package/dist/{chunk-VFJJGXTK.js → chunk-7IOW6LML.js} +6 -1
- package/dist/{chunk-PH67TSYW.js → chunk-JQCUVXXD.js} +302 -18
- package/dist/{chunk-66FFQMHI.js → chunk-M74ENUWR.js} +39 -6
- package/dist/chunk-NRLF6MGR.js +130 -0
- package/dist/cli.js +8 -3
- package/dist/client.d.ts +5 -1
- package/dist/client.js +1 -0
- package/dist/embed.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/live-worker.d.ts +2 -2
- package/dist/live-worker.js +2 -2
- package/dist/live.d.ts +5 -2
- package/dist/live.js +7 -0
- package/dist/{read-CeWoSAAy.d.ts → read--Jd3kE13.d.ts} +3 -9
- package/dist/{service-Ckz-3qhK.d.ts → service-OiTtNadu.d.ts} +11 -2
- package/dist/store-file.d.ts +1 -12
- package/dist/store-file.js +1 -1
- package/dist/ui.css +2 -2
- package/dist/worker.d.ts +104 -7
- package/dist/worker.js +4 -2
- package/llms.txt +3 -3
- package/package.json +9 -5
- package/dist/chunk-UKST5A57.js +0 -228
|
@@ -34,6 +34,22 @@ function boardKey(id) {
|
|
|
34
34
|
}
|
|
35
35
|
return id;
|
|
36
36
|
}
|
|
37
|
+
function isStoreId(id) {
|
|
38
|
+
try {
|
|
39
|
+
assertId(id);
|
|
40
|
+
return /^[A-Za-z0-9_-]{1,128}$/.test(id);
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function isBoardId(id) {
|
|
46
|
+
try {
|
|
47
|
+
boardKey(id);
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
37
53
|
function storeFailure(error) {
|
|
38
54
|
if (error instanceof StoreConflictError) throw new CanvasError("UNAVAILABLE", "the board changed at the same time. Try again");
|
|
39
55
|
if (error instanceof FileStoreError) throw new CanvasError(error.code === "LOCKED" ? "UNAVAILABLE" : "INVALID", error.message);
|
|
@@ -57,12 +73,20 @@ var CanvasService = class {
|
|
|
57
73
|
/** The first 100 boards (the store sorts by id), with titles. A board whose stored bytes are damaged shows an empty title: it stays in the list so a person can find and repair it. */
|
|
58
74
|
async list() {
|
|
59
75
|
const page = await this.store.query(COLLECTION, { limit: 100 }).catch(storeFailure);
|
|
60
|
-
return page.items.map(({ id, data }) =>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
return page.items.map(({ id, data }) => summary(id, data));
|
|
77
|
+
}
|
|
78
|
+
/** The listed boards that exist, with titles, in the order given. */
|
|
79
|
+
async summaries(ids) {
|
|
80
|
+
const found = await Promise.all(ids.map(async (id) => ({ id, data: await this.store.get(COLLECTION, id).catch(storeFailure) })));
|
|
81
|
+
return found.flatMap(({ id, data }) => data ? [summary(id, data)] : []);
|
|
82
|
+
}
|
|
83
|
+
/** Delete a board and everything on it. Throws NOT_FOUND unless the board exists. The Worker erases the live room too. */
|
|
84
|
+
async delete(id) {
|
|
85
|
+
boardKey(id);
|
|
86
|
+
return this.store.transaction(async (tx) => {
|
|
87
|
+
if (!await tx.get(COLLECTION, id)) throw new CanvasError("NOT_FOUND", `no board "${id}"`);
|
|
88
|
+
await tx.delete(COLLECTION, id);
|
|
89
|
+
return { deleted: id };
|
|
66
90
|
});
|
|
67
91
|
}
|
|
68
92
|
/** Throws NOT_FOUND unless the board exists. A live room opens only for a board that exists. */
|
|
@@ -182,6 +206,13 @@ var CanvasService = class {
|
|
|
182
206
|
return decodeStored(doc, id, Board.decode);
|
|
183
207
|
}
|
|
184
208
|
};
|
|
209
|
+
function summary(id, data) {
|
|
210
|
+
try {
|
|
211
|
+
return { id, title: decodeStored(data, id, Board.decode).title };
|
|
212
|
+
} catch {
|
|
213
|
+
return { id, title: "" };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
185
216
|
function decodeStored(doc, id, decode) {
|
|
186
217
|
try {
|
|
187
218
|
return decode(fromBase64(String(doc.update)));
|
|
@@ -193,5 +224,7 @@ function decodeStored(doc, id, decode) {
|
|
|
193
224
|
|
|
194
225
|
export {
|
|
195
226
|
MAX_LIVE_BYTES,
|
|
227
|
+
isStoreId,
|
|
228
|
+
isBoardId,
|
|
196
229
|
CanvasService
|
|
197
230
|
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FileStoreError
|
|
3
|
+
} from "./chunk-NWQED42L.js";
|
|
4
|
+
|
|
5
|
+
// src/store-file.ts
|
|
6
|
+
import { randomUUID } from "crypto";
|
|
7
|
+
import { closeSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { createFileStore as createIdentityFileStore } from "agentic-identity/file";
|
|
10
|
+
import { StoreConflictError } from "agentic-identity";
|
|
11
|
+
var RETRY_FOR_MS = 1e4;
|
|
12
|
+
var LOCK_STALE_MS = 3e3;
|
|
13
|
+
var LOCK_WAIT_MS = 5e3;
|
|
14
|
+
var HELD = /locked by another process/;
|
|
15
|
+
var NOT_A_STORE_FILE = /is not an agentic-identity store file/;
|
|
16
|
+
async function retried(run) {
|
|
17
|
+
const deadline = Date.now() + RETRY_FOR_MS;
|
|
18
|
+
for (; ; ) {
|
|
19
|
+
try {
|
|
20
|
+
return await run();
|
|
21
|
+
} catch (error) {
|
|
22
|
+
const clash = error instanceof StoreConflictError && !HELD.test(error.message);
|
|
23
|
+
if (!clash || Date.now() > deadline) throw error;
|
|
24
|
+
await new Promise((resolve) => setTimeout(resolve, 5 + Math.random() * 20));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
var isDoc = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29
|
+
var sleep = (ms) => void Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
30
|
+
function legacyCollections(path) {
|
|
31
|
+
try {
|
|
32
|
+
const data = JSON.parse(readFileSync(path, "utf8"));
|
|
33
|
+
const current = isDoc(data) && data.format === 1 && isDoc(data.collections);
|
|
34
|
+
const old = isDoc(data) && !current && Object.values(data).every((docs) => isDoc(docs) && Object.values(docs).every(isDoc));
|
|
35
|
+
return old ? data : void 0;
|
|
36
|
+
} catch {
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function withLock(path, fn) {
|
|
41
|
+
const lock = `${path}.lock`;
|
|
42
|
+
const mine = `${process.pid}:${randomUUID()}`;
|
|
43
|
+
for (const start = Date.now(); ; ) {
|
|
44
|
+
try {
|
|
45
|
+
writeFileSync(lock, mine, { flag: "wx", mode: 384 });
|
|
46
|
+
break;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error.code !== "EEXIST") return false;
|
|
49
|
+
try {
|
|
50
|
+
if (Date.now() - statSync(lock).mtimeMs > LOCK_STALE_MS) unlinkSync(lock);
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
if (Date.now() - start > LOCK_WAIT_MS) return false;
|
|
54
|
+
sleep(10);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const owned = () => {
|
|
58
|
+
try {
|
|
59
|
+
return readFileSync(lock, "utf8") === mine;
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
try {
|
|
65
|
+
fn(owned);
|
|
66
|
+
} finally {
|
|
67
|
+
if (owned()) rmSync(lock, { force: true });
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
function upgradeLegacyFile(path) {
|
|
72
|
+
if (legacyCollections(path) === void 0) return true;
|
|
73
|
+
try {
|
|
74
|
+
withLock(path, (owned) => {
|
|
75
|
+
const data = legacyCollections(path);
|
|
76
|
+
if (data === void 0) return;
|
|
77
|
+
const temp = `${path}.${process.pid}.upgrade`;
|
|
78
|
+
try {
|
|
79
|
+
const fd = openSync(temp, "w", 384);
|
|
80
|
+
try {
|
|
81
|
+
writeSync(fd, JSON.stringify({ format: 1, collections: data }));
|
|
82
|
+
fsyncSync(fd);
|
|
83
|
+
} finally {
|
|
84
|
+
closeSync(fd);
|
|
85
|
+
}
|
|
86
|
+
if (owned()) renameSync(temp, path);
|
|
87
|
+
} finally {
|
|
88
|
+
rmSync(temp, { force: true });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
return legacyCollections(path) === void 0;
|
|
94
|
+
}
|
|
95
|
+
function createFileStore(path, options = {}) {
|
|
96
|
+
upgradeLegacyFile(path);
|
|
97
|
+
const inner = createIdentityFileStore({ ...options, path });
|
|
98
|
+
const guard = async (run) => {
|
|
99
|
+
for (let upgraded = false; ; upgraded = true) {
|
|
100
|
+
try {
|
|
101
|
+
return await retried(run);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (!(error instanceof Error)) throw error;
|
|
104
|
+
if (HELD.test(error.message)) throw new FileStoreError("LOCKED", "the database file is locked. If no agentic-canvas process runs, remove the .lock file next to it");
|
|
105
|
+
if (!NOT_A_STORE_FILE.test(error.message)) throw error;
|
|
106
|
+
if (legacyCollections(path) === void 0) throw new FileStoreError("DAMAGED", "the database file is not valid JSON. Repair or remove it");
|
|
107
|
+
if (upgraded || !upgradeLegacyFile(path)) throw new FileStoreError("DAMAGED", "the database file is in the older layout and could not be upgraded. Try again, and check that its folder is writable");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
region: inner.region,
|
|
113
|
+
get: (collection, id) => guard(() => inner.get(collection, id)),
|
|
114
|
+
query: (collection, query) => guard(() => inner.query(collection, query)),
|
|
115
|
+
put: (collection, id, doc) => guard(() => inner.put(collection, id, doc)),
|
|
116
|
+
delete: (collection, id) => guard(() => inner.delete(collection, id)),
|
|
117
|
+
transaction: (fn) => guard(() => inner.transaction(fn))
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function openLocalStore(dir) {
|
|
121
|
+
mkdirSync(dir, { recursive: true });
|
|
122
|
+
return createFileStore(join(dir, STORE_FILE));
|
|
123
|
+
}
|
|
124
|
+
var STORE_FILE = "canvas.json";
|
|
125
|
+
|
|
126
|
+
export {
|
|
127
|
+
createFileStore,
|
|
128
|
+
openLocalStore,
|
|
129
|
+
STORE_FILE
|
|
130
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
operatorActor,
|
|
6
6
|
runMcp,
|
|
7
7
|
svgToPng
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-7IOW6LML.js";
|
|
9
9
|
import {
|
|
10
10
|
openLocalStore
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-NRLF6MGR.js";
|
|
12
12
|
import {
|
|
13
13
|
CanvasService
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-M74ENUWR.js";
|
|
15
15
|
import "./chunk-NWQED42L.js";
|
|
16
16
|
import {
|
|
17
17
|
CanvasError
|
|
@@ -36,6 +36,7 @@ Commands:
|
|
|
36
36
|
connect <board> <from> <to> [--text <t>] Join two shapes with an arrow.
|
|
37
37
|
update <board> <element> [--x --y --w --h --text --fill --stroke] Change fields of an element. Resize with --w and --h.
|
|
38
38
|
move <board> <element> --dx <n> --dy <n> Move an element or a group.
|
|
39
|
+
delete <board> Delete a whole board for good.
|
|
39
40
|
rm <board> <element> Delete an element.
|
|
40
41
|
flow <board> --text <t> | --file <p> Draw a flowchart from text ("A -> B: label"). Use --file - for stdin.
|
|
41
42
|
export <board> [--format svg|png] [--out <path>] Print SVG, or write PNG to --out.
|
|
@@ -141,6 +142,10 @@ async function run(argv) {
|
|
|
141
142
|
case "move":
|
|
142
143
|
need(2, "<board> <element> --dx <n> --dy <n>");
|
|
143
144
|
return print(await service.move(board, rest[0], num(v.dx) ?? Number.NaN, num(v.dy) ?? Number.NaN, actor()));
|
|
145
|
+
case "delete":
|
|
146
|
+
need(1, "<board>");
|
|
147
|
+
actor();
|
|
148
|
+
return print(await service.delete(board));
|
|
144
149
|
case "rm":
|
|
145
150
|
need(2, "<board> <element>");
|
|
146
151
|
return print(await service.remove(board, rest[0]));
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { E as ErrorCode, a as BoardData, c as ShapeType, S as Shape, d as Endpoint, C as Connector } from './read
|
|
1
|
+
import { E as ErrorCode, a as BoardData, c as ShapeType, S as Shape, d as Endpoint, C as Connector } from './read--Jd3kE13.js';
|
|
2
2
|
import { PrincipalKind } from 'agentic-identity';
|
|
3
3
|
import 'yjs';
|
|
4
4
|
import 'zod';
|
|
@@ -81,6 +81,10 @@ interface CanvasClient {
|
|
|
81
81
|
readBoard(id: string, options?: CallOptions): Promise<BoardData & {
|
|
82
82
|
id: string;
|
|
83
83
|
}>;
|
|
84
|
+
/** Delete a board for good, with its live room. Returns the id. */
|
|
85
|
+
deleteBoard(id: string, options?: CallOptions): Promise<{
|
|
86
|
+
deleted: string;
|
|
87
|
+
}>;
|
|
84
88
|
/** The board as an SVG document. */
|
|
85
89
|
exportSvg(id: string, options?: CallOptions): Promise<string>;
|
|
86
90
|
addShape(id: string, shape: ShapeInput, options?: CallOptions): Promise<Shape>;
|
package/dist/client.js
CHANGED
|
@@ -58,6 +58,7 @@ function createClient(options) {
|
|
|
58
58
|
listBoards: (o) => json("GET", "/boards", void 0, o),
|
|
59
59
|
createBoard: (input = {}, o) => json("POST", "/boards", input, o),
|
|
60
60
|
readBoard: (id, o) => json("GET", `/boards/${seg(id)}`, void 0, o),
|
|
61
|
+
deleteBoard: (id, o) => json("DELETE", `/boards/${seg(id)}`, void 0, o),
|
|
61
62
|
exportSvg: async (id, o) => (await send("GET", `/boards/${seg(id)}/svg`, {}, o)).text(),
|
|
62
63
|
addShape: (id, shape, o) => json("POST", `/boards/${seg(id)}/shapes`, shape, o),
|
|
63
64
|
addConnector: (id, connector, o) => json("POST", `/boards/${seg(id)}/connectors`, connector, o),
|
package/dist/embed.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { B as Board, P as Principal } from './read
|
|
2
|
-
export { a as BoardData, b as CanvasElement, e as CanvasError, C as Connector, f as EdgeData, d as Endpoint, E as ErrorCode, G as Group, M as MAX_ELEMENTS, g as MAX_TEXT, N as NodeData, h as SHAPE_TYPES, S as Shape, c as ShapeType, p as principalSchema, r as readBoard } from './read
|
|
3
|
-
import { B as BoardAccess } from './service-
|
|
4
|
-
export { C as CanvasService, D as DrawOptions, F as FlowEdge, a as FlowGraph, b as FlowNode, M as MAX_FLOW_EDGES, c as MAX_FLOW_NODES, P as Placement, d as drawFlow, l as layoutFlow, p as parseFlow } from './service-
|
|
1
|
+
import { B as Board, P as Principal } from './read--Jd3kE13.js';
|
|
2
|
+
export { a as BoardData, b as CanvasElement, e as CanvasError, C as Connector, f as EdgeData, d as Endpoint, E as ErrorCode, G as Group, M as MAX_ELEMENTS, g as MAX_TEXT, N as NodeData, h as SHAPE_TYPES, S as Shape, c as ShapeType, p as principalSchema, r as readBoard } from './read--Jd3kE13.js';
|
|
3
|
+
import { B as BoardAccess } from './service-OiTtNadu.js';
|
|
4
|
+
export { C as CanvasService, D as DrawOptions, F as FlowEdge, a as FlowGraph, b as FlowNode, M as MAX_FLOW_EDGES, c as MAX_FLOW_NODES, P as Placement, d as drawFlow, l as layoutFlow, p as parseFlow } from './service-OiTtNadu.js';
|
|
5
5
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
6
|
export { FileStoreOptions, createFileStore, openLocalStore } from './store-file.js';
|
|
7
7
|
export { PRINCIPAL_KINDS, PrincipalKind, isMachineMade } from 'agentic-identity';
|
package/dist/index.js
CHANGED
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
operatorActor,
|
|
7
7
|
runMcp,
|
|
8
8
|
svgToPng
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-7IOW6LML.js";
|
|
10
10
|
import {
|
|
11
11
|
createFileStore,
|
|
12
12
|
openLocalStore
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-NRLF6MGR.js";
|
|
14
14
|
import {
|
|
15
15
|
CanvasService
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-M74ENUWR.js";
|
|
17
17
|
import "./chunk-NWQED42L.js";
|
|
18
18
|
import {
|
|
19
19
|
Board,
|
package/dist/live-worker.d.ts
CHANGED
|
@@ -5,10 +5,10 @@ import 'agentic-identity';
|
|
|
5
5
|
import 'agentic-realtime';
|
|
6
6
|
import 'agentic-identity/cloudflare';
|
|
7
7
|
import 'hono';
|
|
8
|
-
import './read
|
|
8
|
+
import './read--Jd3kE13.js';
|
|
9
9
|
import 'yjs';
|
|
10
10
|
import 'zod';
|
|
11
|
-
import './service-
|
|
11
|
+
import './service-OiTtNadu.js';
|
|
12
12
|
|
|
13
13
|
type Namespace = Parameters<typeof createDurableObjectRealtime>[0];
|
|
14
14
|
interface LiveEnv extends WorkerEnv {
|
package/dist/live-worker.js
CHANGED
package/dist/live.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { B as Board, P as Principal, a as BoardData, S as Shape, C as Connector, b as CanvasElement } from './read
|
|
1
|
+
import { B as Board, P as Principal, a as BoardData, S as Shape, C as Connector, b as CanvasElement } from './read--Jd3kE13.js';
|
|
2
2
|
import { Peer } from 'agentic-realtime/client';
|
|
3
|
-
import { B as BoardAccess, D as DrawOptions } from './service-
|
|
3
|
+
import { B as BoardAccess, D as DrawOptions } from './service-OiTtNadu.js';
|
|
4
4
|
import 'yjs';
|
|
5
5
|
import 'agentic-identity';
|
|
6
6
|
import 'zod';
|
|
@@ -83,6 +83,9 @@ declare class LiveService implements BoardAccess {
|
|
|
83
83
|
id: string;
|
|
84
84
|
title: string;
|
|
85
85
|
}[]>;
|
|
86
|
+
delete(id: string): Promise<{
|
|
87
|
+
deleted: string;
|
|
88
|
+
}>;
|
|
86
89
|
read(id: string): Promise<BoardData & {
|
|
87
90
|
id: string;
|
|
88
91
|
}>;
|
package/dist/live.js
CHANGED
|
@@ -145,6 +145,13 @@ var LiveService = class {
|
|
|
145
145
|
if (!Array.isArray(boards) || !boards.every((b) => typeof b?.id === "string" && typeof b?.title === "string")) throw new CanvasError("INVALID", "the server sent a board list this client does not know");
|
|
146
146
|
return boards;
|
|
147
147
|
}
|
|
148
|
+
async delete(id) {
|
|
149
|
+
const response = await call(this.options.server, this.options.token, `/boards/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
150
|
+
if (!response.ok) throw await refusal(response, "the server refused the request");
|
|
151
|
+
const { deleted } = await json(response);
|
|
152
|
+
if (deleted !== id) throw new CanvasError("INVALID", "the server sent a result this client does not know");
|
|
153
|
+
return { deleted };
|
|
154
|
+
}
|
|
148
155
|
async read(id) {
|
|
149
156
|
return this.use(id, async (live) => ({ id, ...readBoard(live.board) }));
|
|
150
157
|
}
|
|
@@ -6,22 +6,16 @@ import { z } from 'zod';
|
|
|
6
6
|
* The actor of a new write. The rule is the identity kernel's `parsePrincipal`: a known kind, and an id of 1 to 128
|
|
7
7
|
* characters from A-Z, a-z, 0-9, ".", "_" and "-". The Worker, the CLI and the SDK all use it.
|
|
8
8
|
*/
|
|
9
|
-
declare const principalSchema: z.
|
|
9
|
+
declare const principalSchema: z.ZodPipe<z.ZodObject<{
|
|
10
10
|
kind: z.ZodString;
|
|
11
11
|
id: z.ZodString;
|
|
12
|
-
},
|
|
13
|
-
kind: string;
|
|
14
|
-
id: string;
|
|
15
|
-
}, {
|
|
16
|
-
kind: string;
|
|
17
|
-
id: string;
|
|
18
|
-
}>, {
|
|
12
|
+
}, z.core.$strict>, z.ZodTransform<{
|
|
19
13
|
kind: PrincipalKind;
|
|
20
14
|
id: string;
|
|
21
15
|
}, {
|
|
22
16
|
kind: string;
|
|
23
17
|
id: string;
|
|
24
|
-
}
|
|
18
|
+
}>>;
|
|
25
19
|
type Principal = {
|
|
26
20
|
kind: PrincipalKind;
|
|
27
21
|
id: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as ShapeType, B as Board, P as Principal, a as BoardData, S as Shape, C as Connector, b as CanvasElement } from './read
|
|
1
|
+
import { c as ShapeType, B as Board, P as Principal, a as BoardData, S as Shape, C as Connector, b as CanvasElement } from './read--Jd3kE13.js';
|
|
2
2
|
import { Store } from 'agentic-identity';
|
|
3
3
|
|
|
4
4
|
declare const MAX_FLOW_NODES = 500;
|
|
@@ -68,6 +68,15 @@ declare class CanvasService {
|
|
|
68
68
|
id: string;
|
|
69
69
|
title: string;
|
|
70
70
|
}[]>;
|
|
71
|
+
/** The listed boards that exist, with titles, in the order given. */
|
|
72
|
+
summaries(ids: string[]): Promise<{
|
|
73
|
+
id: string;
|
|
74
|
+
title: string;
|
|
75
|
+
}[]>;
|
|
76
|
+
/** Delete a board and everything on it. Throws NOT_FOUND unless the board exists. The Worker erases the live room too. */
|
|
77
|
+
delete(id: string): Promise<{
|
|
78
|
+
deleted: string;
|
|
79
|
+
}>;
|
|
71
80
|
/** Throws NOT_FOUND unless the board exists. A live room opens only for a board that exists. */
|
|
72
81
|
assertExists(id: string): Promise<void>;
|
|
73
82
|
read(id: string): Promise<BoardData & {
|
|
@@ -123,6 +132,6 @@ declare class CanvasService {
|
|
|
123
132
|
private load;
|
|
124
133
|
}
|
|
125
134
|
/** What the CLI and the MCP server need. `CanvasService` runs it on a store, and `LiveService` runs it in a live room. */
|
|
126
|
-
type BoardAccess = Pick<CanvasService, "create" | "list" | "read" | "update" | "add" | "connect" | "move" | "remove" | "flow" | "exportSvg">;
|
|
135
|
+
type BoardAccess = Pick<CanvasService, "create" | "list" | "read" | "delete" | "update" | "add" | "connect" | "move" | "remove" | "flow" | "exportSvg">;
|
|
127
136
|
|
|
128
137
|
export { type BoardAccess as B, CanvasService as C, type DrawOptions as D, type FlowEdge as F, MAX_FLOW_EDGES as M, type Placement as P, type FlowGraph as a, type FlowNode as b, MAX_FLOW_NODES as c, drawFlow as d, layoutFlow as l, parseFlow as p };
|
package/dist/store-file.d.ts
CHANGED
|
@@ -7,19 +7,8 @@ declare class FileStoreError extends Error {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
interface FileStoreOptions extends AdapterOptions {
|
|
10
|
-
/** How long a write waits for the lock before it fails. Defaults to 10000. */
|
|
11
|
-
lockTimeoutMs?: number;
|
|
12
|
-
/** A lock older than this is stale, if its owner is alive. Defaults to 30000. */
|
|
13
|
-
staleAfterMs?: number;
|
|
14
10
|
}
|
|
15
|
-
/**
|
|
16
|
-
* A Store kept in one JSON file. Node only. It is an adapter of the shared Store port and passes `runStoreContract`. Many processes may share the file, for example the CLI
|
|
17
|
-
* and the MCP server. Each write takes a lock file, reads the file again, applies the change, and
|
|
18
|
-
* replaces the file by an atomic rename. A read sees the last renamed file, so it takes no lock.
|
|
19
|
-
* Each call reads the whole file and loads the docs it needs into the shared memory adapter, which runs the
|
|
20
|
-
* queries and the transaction rules. Cost grows with the file. Use it for local work, not for scale.
|
|
21
|
-
* The lock needs one machine: the process check and the clock do not work across hosts.
|
|
22
|
-
*/
|
|
11
|
+
/** A Store kept in one JSON file. Node only. It is the file adapter of agentic-identity, with a retry on a write clash. */
|
|
23
12
|
declare function createFileStore(path: string, options?: FileStoreOptions): Store;
|
|
24
13
|
/** The store the CLI and the MCP server share: one file in `dir`, which is made if it is missing. */
|
|
25
14
|
declare function openLocalStore(dir: string): Store;
|
package/dist/store-file.js
CHANGED
package/dist/ui.css
CHANGED
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
.shape { fill: var(--ad-color-surface-raised); stroke: var(--ad-color-border-strong); stroke-width: 1.5; }
|
|
16
16
|
.shape.text { fill: transparent; stroke: none; }
|
|
17
17
|
.shape.frame, .shape.image { fill: none; stroke-dasharray: 6 4; pointer-events: visiblePainted; }
|
|
18
|
-
.shape.sticky { fill:
|
|
18
|
+
.shape.sticky { fill: var(--ad-color-warning-subtle); stroke: var(--ad-color-warning); }
|
|
19
19
|
.label { fill: var(--ad-color-text); pointer-events: none; }
|
|
20
|
-
.sticky-label { fill:
|
|
20
|
+
.sticky-label { fill: var(--ad-color-warning-text); }
|
|
21
21
|
.edge { stroke: var(--ad-color-text-muted); stroke-width: 2; fill: none; pointer-events: none; }
|
|
22
22
|
.hit { stroke: transparent; stroke-width: 14; }
|
|
23
23
|
.arrowhead { fill: var(--ad-color-text-muted); }
|
package/dist/worker.d.ts
CHANGED
|
@@ -1,20 +1,101 @@
|
|
|
1
|
-
import { Blobs } from 'agentic-identity';
|
|
1
|
+
import { Store, PrincipalContext, Action, Grant, InviteSummary, Blobs } from 'agentic-identity';
|
|
2
2
|
import { Realtime } from 'agentic-realtime';
|
|
3
3
|
import { D1Like, R2Like } from 'agentic-identity/cloudflare';
|
|
4
4
|
import { Hono } from 'hono';
|
|
5
|
-
import { P as Principal } from './read
|
|
6
|
-
import { C as CanvasService } from './service-
|
|
5
|
+
import { P as Principal } from './read--Jd3kE13.js';
|
|
6
|
+
import { C as CanvasService } from './service-OiTtNadu.js';
|
|
7
7
|
import 'yjs';
|
|
8
8
|
import 'zod';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Who may do what on a board. A board is an identity resource of type `canvas`. The roles, the grants, the invites,
|
|
12
|
+
* and the links are those of agentic-identity: this class checks input and maps its failures to `CanvasError`.
|
|
13
|
+
* A caller who cannot read a board gets NOT_FOUND, so a board id tells a stranger nothing. A caller who can read
|
|
14
|
+
* it and lacks the role for the action gets FORBIDDEN.
|
|
15
|
+
*/
|
|
16
|
+
declare class CanvasAccess {
|
|
17
|
+
private readonly store;
|
|
18
|
+
private readonly sharing;
|
|
19
|
+
private readonly now;
|
|
20
|
+
constructor(store: Store, options?: {
|
|
21
|
+
now?: () => number;
|
|
22
|
+
});
|
|
23
|
+
/** A board id that the Store refuses names no board. The gate checks this for a role holder: a token of the dev list skips the gate. */
|
|
24
|
+
private known;
|
|
25
|
+
/** Make the actor the owner of a new board. */
|
|
26
|
+
register(ctx: PrincipalContext, id: string): Promise<void>;
|
|
27
|
+
/** Remove the sharing records of a board. Needs an owner. */
|
|
28
|
+
unregister(ctx: PrincipalContext, id: string): Promise<void>;
|
|
29
|
+
/** True if the board has sharing records: it was made by an identity credential. */
|
|
30
|
+
registered(id: string): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* True if the caller may do `action` on the board. A link token adds the role of its link. It writes an audit entry.
|
|
33
|
+
* An id that belongs to a resource of another type, in a Store shared with another app, is no board: it is denied.
|
|
34
|
+
*/
|
|
35
|
+
allowed(ctx: PrincipalContext | null, action: Action, id: string, linkToken?: string): Promise<boolean>;
|
|
36
|
+
/** Throw NOT_FOUND for a caller who cannot read the board, and FORBIDDEN for one who can read it but not do `action`. */
|
|
37
|
+
require(ctx: PrincipalContext | null, action: Action, id: string, linkToken?: string): Promise<void>;
|
|
38
|
+
/** The ids of the boards a person or agent holds a role on, the first 100. A credential without `canvas:read` gets none. */
|
|
39
|
+
readable(ctx: PrincipalContext): Promise<string[]>;
|
|
40
|
+
grant(ctx: PrincipalContext, id: string, input: {
|
|
41
|
+
subjectKind?: unknown;
|
|
42
|
+
subjectId?: unknown;
|
|
43
|
+
role?: unknown;
|
|
44
|
+
}): Promise<{
|
|
45
|
+
ok: true;
|
|
46
|
+
}>;
|
|
47
|
+
revoke(ctx: PrincipalContext, id: string, input: {
|
|
48
|
+
subjectKind?: unknown;
|
|
49
|
+
subjectId?: unknown;
|
|
50
|
+
}): Promise<{
|
|
51
|
+
ok: true;
|
|
52
|
+
}>;
|
|
53
|
+
/** The grants and the invites on a board. An invite never shows its email. Links are not listed: a token shows once. */
|
|
54
|
+
list(ctx: PrincipalContext, id: string): Promise<{
|
|
55
|
+
grants: Grant[];
|
|
56
|
+
invites: InviteSummary[];
|
|
57
|
+
}>;
|
|
58
|
+
invite(ctx: PrincipalContext, id: string, input: {
|
|
59
|
+
role?: unknown;
|
|
60
|
+
email?: unknown;
|
|
61
|
+
handle?: unknown;
|
|
62
|
+
expiresInMs?: unknown;
|
|
63
|
+
}): Promise<{
|
|
64
|
+
id: string;
|
|
65
|
+
}>;
|
|
66
|
+
/** The email comes from a verified sign-in and the handle from the agent's own record, never from the request. */
|
|
67
|
+
accept(ctx: PrincipalContext, inviteId: string): Promise<{
|
|
68
|
+
ok: true;
|
|
69
|
+
}>;
|
|
70
|
+
/** The token shows once. Only its hash is stored. */
|
|
71
|
+
createLink(ctx: PrincipalContext, id: string, input: {
|
|
72
|
+
role?: unknown;
|
|
73
|
+
signedInOnly?: unknown;
|
|
74
|
+
expiresInMs?: unknown;
|
|
75
|
+
}): Promise<{
|
|
76
|
+
id: string;
|
|
77
|
+
token: string;
|
|
78
|
+
}>;
|
|
79
|
+
/** Revoke a link of this board. A link of another board is NOT_FOUND here, even for an owner of both. */
|
|
80
|
+
revokeLink(ctx: PrincipalContext, id: string, linkId: string): Promise<{
|
|
81
|
+
ok: true;
|
|
82
|
+
}>;
|
|
83
|
+
}
|
|
84
|
+
|
|
10
85
|
interface WorkerEnv {
|
|
11
86
|
DB: D1Like;
|
|
12
87
|
IMAGES: R2Like;
|
|
13
88
|
/**
|
|
89
|
+
* Dev fallback. Set it to "1" to let `CANVAS_API_TOKENS` sign requests in. Without it, `CANVAS_API_TOKENS` is not read,
|
|
90
|
+
* and a request that sets it gets 503, so a deploy never trusts a static list by accident. Never set it in production.
|
|
91
|
+
*/
|
|
92
|
+
CANVAS_UNSAFE_STATIC_TOKENS?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Dev fallback, read only when `CANVAS_UNSAFE_STATIC_TOKENS` is "1". It is tried after the identity credentials.
|
|
14
95
|
* JSON: `{ "<token>": { "actor": "ada", "kind": "human", "role": "viewer" } }`. A token has 32 characters or more.
|
|
15
96
|
* A grant needs an actor id and a kind. A token with a missing or bad part gets 401.
|
|
16
97
|
* The role is "editor" (the default) or "viewer". A viewer reads and cannot write, over HTTP or in a live room.
|
|
17
|
-
*
|
|
98
|
+
* Every token reaches every board, and a token cannot be revoked without a new deploy.
|
|
18
99
|
*/
|
|
19
100
|
CANVAS_API_TOKENS?: string;
|
|
20
101
|
/**
|
|
@@ -27,13 +108,22 @@ interface WorkerEnv {
|
|
|
27
108
|
CANVAS_MAX_IMAGE_BYTES?: string;
|
|
28
109
|
}
|
|
29
110
|
type Role = "editor" | "viewer";
|
|
111
|
+
/** The caller. `dev` is true for a token of the static list: it holds no board roles and reaches every board. */
|
|
112
|
+
interface Caller {
|
|
113
|
+
actor: Principal;
|
|
114
|
+
role: Role;
|
|
115
|
+
principal: PrincipalContext;
|
|
116
|
+
dev: boolean;
|
|
117
|
+
}
|
|
30
118
|
type App = {
|
|
31
119
|
Bindings: WorkerEnv;
|
|
32
120
|
Variables: {
|
|
33
121
|
service: CanvasService;
|
|
122
|
+
access: CanvasAccess;
|
|
34
123
|
blobs: Blobs;
|
|
35
124
|
actor: Principal;
|
|
36
125
|
role: Role;
|
|
126
|
+
caller: Caller;
|
|
37
127
|
};
|
|
38
128
|
};
|
|
39
129
|
interface AppOptions {
|
|
@@ -42,11 +132,18 @@ interface AppOptions {
|
|
|
42
132
|
}
|
|
43
133
|
/** A browser cannot set a header on a WebSocket. It offers the sub-protocols `agentic-canvas` and `agentic-canvas.bearer.<token in base64url>`. */
|
|
44
134
|
declare const WS_PROTOCOL = "agentic-canvas";
|
|
45
|
-
/**
|
|
46
|
-
|
|
135
|
+
/**
|
|
136
|
+
* The principal and role for a request, or null. A credential issued by agentic-identity (a bearer key or an access token in the
|
|
137
|
+
* store) signs in. Its scopes set the role: `canvas:write` is an editor, `canvas:read` a viewer, and none is denied.
|
|
138
|
+
* The static list signs in only with `CANVAS_UNSAFE_STATIC_TOKENS=1`. The kind is never defaulted: a principal without a valid kind gets null.
|
|
139
|
+
* Without `store` only the static list is read.
|
|
140
|
+
*/
|
|
141
|
+
declare function authenticate(request: Request, env: WorkerEnv, store?: Store): Promise<{
|
|
47
142
|
actor: Principal;
|
|
48
143
|
role: Role;
|
|
49
144
|
} | null>;
|
|
145
|
+
/** Like `authenticate`, with the whole principal context that board roles are checked against. */
|
|
146
|
+
declare function identify(request: Request, env: WorkerEnv, store?: Store): Promise<Caller | null>;
|
|
50
147
|
/** The image types we accept, found by their first bytes. The client's claim of a type is not trusted. */
|
|
51
148
|
declare function sniffImage(bytes: Uint8Array): string | null;
|
|
52
149
|
declare function createApp(options?: AppOptions): Hono<App>;
|
|
@@ -54,4 +151,4 @@ declare const _default: {
|
|
|
54
151
|
fetch: (request: Request, env: WorkerEnv, ctx: unknown) => Response | Promise<Response>;
|
|
55
152
|
};
|
|
56
153
|
|
|
57
|
-
export { type AppOptions, type Role, WS_PROTOCOL, type WorkerEnv, authenticate, createApp, _default as default, sniffImage };
|
|
154
|
+
export { type AppOptions, type Caller, type Role, WS_PROTOCOL, type WorkerEnv, authenticate, createApp, _default as default, identify, sniffImage };
|
package/dist/worker.js
CHANGED
|
@@ -2,10 +2,11 @@ import {
|
|
|
2
2
|
WS_PROTOCOL,
|
|
3
3
|
authenticate,
|
|
4
4
|
createApp,
|
|
5
|
+
identify,
|
|
5
6
|
sniffImage,
|
|
6
7
|
worker_default
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-JQCUVXXD.js";
|
|
9
|
+
import "./chunk-M74ENUWR.js";
|
|
9
10
|
import "./chunk-NWQED42L.js";
|
|
10
11
|
import "./chunk-ZSPZMGHG.js";
|
|
11
12
|
import "./chunk-JOBTIAFJ.js";
|
|
@@ -14,5 +15,6 @@ export {
|
|
|
14
15
|
authenticate,
|
|
15
16
|
createApp,
|
|
16
17
|
worker_default as default,
|
|
18
|
+
identify,
|
|
17
19
|
sniffImage
|
|
18
20
|
};
|