agentic-canvas 0.1.0 → 0.3.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 +57 -4
- package/SKILL.md +3 -0
- package/dist/chunk-AGODNBN7.js +166 -0
- package/dist/{chunk-NSZ2AC5O.js → chunk-GGQPE65T.js} +88 -297
- package/dist/chunk-NWQED42L.js +13 -0
- package/dist/{chunk-I3EJ5JB7.js → chunk-UKST5A57.js} +4 -13
- package/dist/chunk-V477KSZQ.js +2005 -0
- package/dist/chunk-W7NRP3EX.js +164 -0
- package/dist/cli.js +33 -7
- package/dist/client.d.ts +108 -0
- package/dist/client.js +78 -0
- package/dist/index.d.ts +10 -247
- package/dist/index.js +16 -11
- package/dist/live-worker.d.ts +22 -0
- package/dist/live-worker.js +25 -0
- package/dist/live.d.ts +102 -0
- package/dist/live.js +192 -0
- package/dist/read-BY6f-mmy.d.ts +167 -0
- package/dist/service-Cpl6wKeY.d.ts +109 -0
- package/dist/store-file.js +4 -2
- package/dist/worker.d.ts +55 -0
- package/dist/worker.js +15 -0
- package/llms.txt +3 -0
- package/package.json +23 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { c as Board, P as Principal } from './read-BY6f-mmy.js';
|
|
2
|
+
export { B as BoardData, d as CanvasElement, e as CanvasError, C as Connector, f as EdgeData, b as Endpoint, E as ErrorCode, G as Group, M as MAX_ELEMENTS, g as MAX_TEXT, N as NodeData, h as SHAPE_TYPES, a as Shape, S as ShapeType, p as principalSchema, r as readBoard } from './read-BY6f-mmy.js';
|
|
3
|
+
import { B as BoardAccess } from './service-Cpl6wKeY.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-Cpl6wKeY.js';
|
|
3
5
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
-
import { Store } from 'agentic-identity';
|
|
5
|
-
export { PRINCIPAL_KINDS, PrincipalKind, isMachineMade } from 'agentic-identity';
|
|
6
6
|
export { FileStoreOptions, createFileStore, openLocalStore } from './store-file.js';
|
|
7
|
+
export { PRINCIPAL_KINDS, PrincipalKind, isMachineMade } from 'agentic-identity';
|
|
8
|
+
import 'yjs';
|
|
9
|
+
import 'zod';
|
|
7
10
|
|
|
8
11
|
declare const VERSION: string;
|
|
9
12
|
|
|
@@ -13,207 +16,6 @@ declare const BRAND: {
|
|
|
13
16
|
readonly version: string;
|
|
14
17
|
};
|
|
15
18
|
|
|
16
|
-
declare const principalSchema: z.ZodObject<{
|
|
17
|
-
kind: z.ZodEnum<["human", "agent_of_human", "agent_of_agent", "agent", "user_token"]>;
|
|
18
|
-
id: z.ZodString;
|
|
19
|
-
}, "strict", z.ZodTypeAny, {
|
|
20
|
-
id: string;
|
|
21
|
-
kind: "human" | "agent_of_human" | "agent_of_agent" | "agent" | "user_token";
|
|
22
|
-
}, {
|
|
23
|
-
id: string;
|
|
24
|
-
kind: "human" | "agent_of_human" | "agent_of_agent" | "agent" | "user_token";
|
|
25
|
-
}>;
|
|
26
|
-
type Principal = z.infer<typeof principalSchema>;
|
|
27
|
-
|
|
28
|
-
type ErrorCode = "INVALID" | "DUPLICATE_ID" | "NOT_FOUND" | "BAD_REFERENCE" | "LIMIT" | "UNAVAILABLE" | "FORBIDDEN";
|
|
29
|
-
declare class CanvasError extends Error {
|
|
30
|
-
readonly code: ErrorCode;
|
|
31
|
-
constructor(code: ErrorCode, message: string);
|
|
32
|
-
}
|
|
33
|
-
declare const SHAPE_TYPES: readonly ["rect", "ellipse", "diamond", "text", "sticky", "frame", "image"];
|
|
34
|
-
type ShapeType = (typeof SHAPE_TYPES)[number];
|
|
35
|
-
declare const MAX_ELEMENTS = 10000;
|
|
36
|
-
declare const MAX_TEXT = 10000;
|
|
37
|
-
type Endpoint = {
|
|
38
|
-
id: string;
|
|
39
|
-
} | {
|
|
40
|
-
x: number;
|
|
41
|
-
y: number;
|
|
42
|
-
};
|
|
43
|
-
interface Stamp {
|
|
44
|
-
id: string;
|
|
45
|
-
z: number;
|
|
46
|
-
groupId?: string;
|
|
47
|
-
/** Who made the element. Never changes. */
|
|
48
|
-
author: Principal;
|
|
49
|
-
/** Who made the last edit. */
|
|
50
|
-
editedBy: Principal;
|
|
51
|
-
}
|
|
52
|
-
interface Shape extends Stamp {
|
|
53
|
-
type: ShapeType;
|
|
54
|
-
x: number;
|
|
55
|
-
y: number;
|
|
56
|
-
w: number;
|
|
57
|
-
h: number;
|
|
58
|
-
text: string;
|
|
59
|
-
fill?: string;
|
|
60
|
-
stroke?: string;
|
|
61
|
-
src?: string;
|
|
62
|
-
}
|
|
63
|
-
interface Connector extends Stamp {
|
|
64
|
-
type: "connector";
|
|
65
|
-
from: Endpoint;
|
|
66
|
-
to: Endpoint;
|
|
67
|
-
text: string;
|
|
68
|
-
startArrow: boolean;
|
|
69
|
-
endArrow: boolean;
|
|
70
|
-
stroke?: string;
|
|
71
|
-
}
|
|
72
|
-
interface Group extends Stamp {
|
|
73
|
-
type: "group";
|
|
74
|
-
}
|
|
75
|
-
type CanvasElement = Shape | Connector | Group;
|
|
76
|
-
/**
|
|
77
|
-
* A whiteboard on a Yjs doc. Headless: no UI code. Every write names its actor.
|
|
78
|
-
*
|
|
79
|
-
* A synced Yjs update is untrusted input. Every read checks each stored element against the same
|
|
80
|
-
* rules the write methods enforce, and returns only the checked fields. An entry that fails is
|
|
81
|
-
* hidden at once. `repair()` deletes it, and `Board.decode` runs `repair()`. This holds even
|
|
82
|
-
* when a caller writes to `doc` directly.
|
|
83
|
-
*/
|
|
84
|
-
declare class Board {
|
|
85
|
-
readonly doc: Y.Doc;
|
|
86
|
-
private readonly elements;
|
|
87
|
-
private readonly meta;
|
|
88
|
-
private maxElements;
|
|
89
|
-
constructor(doc?: Y.Doc, options?: {
|
|
90
|
-
maxElements?: number;
|
|
91
|
-
});
|
|
92
|
-
/** Load a saved board with no element checks. Only the owner repair path uses it. */
|
|
93
|
-
static open(update: Uint8Array): Board;
|
|
94
|
-
/**
|
|
95
|
-
* Load a saved board. Refuses a doc past the element cap. Then it deletes every entry that fails
|
|
96
|
-
* the element checks, and repairs what concurrent writers left broken.
|
|
97
|
-
*/
|
|
98
|
-
static decode(update: Uint8Array): Board;
|
|
99
|
-
encode(): Uint8Array;
|
|
100
|
-
/** An independent copy. Edits to it leave this board alone. */
|
|
101
|
-
clone(): Board;
|
|
102
|
-
get title(): string;
|
|
103
|
-
get titleBy(): Principal | undefined;
|
|
104
|
-
setTitle(title: string, actor: Principal): void;
|
|
105
|
-
/** One element. An invalid entry, or a connector with a missing bound end, is hidden. */
|
|
106
|
-
get(elementId: string): CanvasElement | undefined;
|
|
107
|
-
/** All visible elements from back to front. */
|
|
108
|
-
list(): CanvasElement[];
|
|
109
|
-
/**
|
|
110
|
-
* Delete what peers can leave broken: entries that fail the element checks, connectors whose
|
|
111
|
-
* bound end is gone, and members that name a deleted group. Idempotent, so every peer may run it.
|
|
112
|
-
* `drop` names elements the owner removes too, with what they hold: a shape takes its connectors,
|
|
113
|
-
* a group takes its members. Refuses a result still past the cap, and then changes nothing.
|
|
114
|
-
* Returns the ids fixed.
|
|
115
|
-
*/
|
|
116
|
-
repair(drop?: string[]): string[];
|
|
117
|
-
addShape(input: unknown, actor: Principal): Shape;
|
|
118
|
-
addConnector(input: unknown, actor: Principal): Connector;
|
|
119
|
-
update(elementId: string, patch: unknown, actor: Principal): CanvasElement;
|
|
120
|
-
/** Shift an element, or a whole group. Returns the ids that moved. Bound connector ends follow their shapes. */
|
|
121
|
-
move(elementId: string, dx: number, dy: number, actor: Principal): string[];
|
|
122
|
-
/** Delete an element. A shape takes its connectors and a group takes its members. Returns the ids removed. */
|
|
123
|
-
remove(elementId: string): string[];
|
|
124
|
-
group(ids: string[], actor: Principal, groupId?: string): Group;
|
|
125
|
-
/** Remove a group and hand its members to the parent group. */
|
|
126
|
-
ungroup(groupId: string, actor: Principal): void;
|
|
127
|
-
/** Put an element, or a group with its inner order kept, in front of or behind every other element. */
|
|
128
|
-
reorder(elementId: string, to: "front" | "back", actor: Principal): void;
|
|
129
|
-
/** One stored element that passes the checks and whose id matches its key, or undefined. */
|
|
130
|
-
private stored;
|
|
131
|
-
/** Every stored element that passes the checks, from back to front. Refuses a doc past the cap. */
|
|
132
|
-
private storedList;
|
|
133
|
-
/** Like storedList, with no cap check. Repair needs every entry, even on a board past the cap. */
|
|
134
|
-
private allStored;
|
|
135
|
-
/** A connector is detached when a bound end does not name a stored shape. */
|
|
136
|
-
private isDetached;
|
|
137
|
-
private require;
|
|
138
|
-
private checkEnd;
|
|
139
|
-
/** The z value just past the top or the bottom. Not clamped: the caller checks the range. */
|
|
140
|
-
private edgeZ;
|
|
141
|
-
/** The element and, for a group, everything below it. Cycle-safe. */
|
|
142
|
-
private subtree;
|
|
143
|
-
private subtreeIn;
|
|
144
|
-
private create;
|
|
145
|
-
/** Apply field changes and stamp the editor, all in one transaction. An undefined value clears the field. */
|
|
146
|
-
private write;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
declare const MAX_FLOW_NODES = 500;
|
|
150
|
-
declare const MAX_FLOW_EDGES = 2000;
|
|
151
|
-
interface FlowNode {
|
|
152
|
-
id: string;
|
|
153
|
-
text: string;
|
|
154
|
-
shape: Extract<ShapeType, "rect" | "ellipse" | "diamond">;
|
|
155
|
-
}
|
|
156
|
-
interface FlowEdge {
|
|
157
|
-
from: string;
|
|
158
|
-
to: string;
|
|
159
|
-
text: string;
|
|
160
|
-
}
|
|
161
|
-
interface FlowGraph {
|
|
162
|
-
direction: "TB" | "LR";
|
|
163
|
-
nodes: FlowNode[];
|
|
164
|
-
edges: FlowEdge[];
|
|
165
|
-
}
|
|
166
|
-
declare function parseFlow(source: string): FlowGraph;
|
|
167
|
-
interface Placement {
|
|
168
|
-
id: string;
|
|
169
|
-
x: number;
|
|
170
|
-
y: number;
|
|
171
|
-
w: number;
|
|
172
|
-
h: number;
|
|
173
|
-
}
|
|
174
|
-
/** Layered layout. The top-left of the result is (0, 0). The layout breaks cycles and handles self-loops. */
|
|
175
|
-
declare function layoutFlow(graph: FlowGraph): Placement[];
|
|
176
|
-
interface DrawOptions {
|
|
177
|
-
/**
|
|
178
|
-
* Put in front of every id. Default: a new random prefix for each call, so two agents that draw at
|
|
179
|
-
* the same time never write the same id. A prefix you pass is used as given, and it makes ids
|
|
180
|
-
* repeatable. Up to 16 of A-Z, a-z, 0-9, _ and -.
|
|
181
|
-
*/
|
|
182
|
-
idPrefix?: string;
|
|
183
|
-
/** Top-left of the drawing. Default: below the shapes already on the board. */
|
|
184
|
-
origin?: {
|
|
185
|
-
x: number;
|
|
186
|
-
y: number;
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
/** Draw a flow on the board. All or nothing: on any error the board stays as it was. */
|
|
190
|
-
declare function drawFlow(board: Board, source: string, actor: Principal, options?: DrawOptions): {
|
|
191
|
-
nodes: string[];
|
|
192
|
-
edges: string[];
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
interface Labels {
|
|
196
|
-
/** The author is an agent or acts for one. */
|
|
197
|
-
machineMade: boolean;
|
|
198
|
-
/** The last edit came from an agent or one acting for one. */
|
|
199
|
-
lastEditMachineMade: boolean;
|
|
200
|
-
}
|
|
201
|
-
type NodeData = Shape & Labels;
|
|
202
|
-
type EdgeData = Connector & Labels;
|
|
203
|
-
interface BoardData {
|
|
204
|
-
title: string;
|
|
205
|
-
nodes: NodeData[];
|
|
206
|
-
edges: EdgeData[];
|
|
207
|
-
groups: {
|
|
208
|
-
id: string;
|
|
209
|
-
members: string[];
|
|
210
|
-
}[];
|
|
211
|
-
/** Every non-empty text, back to front. */
|
|
212
|
-
texts: string[];
|
|
213
|
-
}
|
|
214
|
-
/** The board as plain data for an agent. Reads only. Embedded image bytes are left out. */
|
|
215
|
-
declare function readBoard(board: Board): BoardData;
|
|
216
|
-
|
|
217
19
|
/** Draw the board, back to front, on a white background. */
|
|
218
20
|
declare function toSvg(board: Board): string;
|
|
219
21
|
|
|
@@ -235,51 +37,12 @@ declare class ActorConfigError extends Error {
|
|
|
235
37
|
*/
|
|
236
38
|
declare function operatorActor(env?: Record<string, string | undefined>): Principal;
|
|
237
39
|
|
|
238
|
-
/** One place for every board operation. The CLI, the MCP server, and the Worker call it. */
|
|
239
|
-
declare class CanvasService {
|
|
240
|
-
private readonly store;
|
|
241
|
-
constructor(store: Store);
|
|
242
|
-
create(actor: Principal, options?: {
|
|
243
|
-
id?: string;
|
|
244
|
-
title?: string;
|
|
245
|
-
}): Promise<{
|
|
246
|
-
id: string;
|
|
247
|
-
title: string;
|
|
248
|
-
}>;
|
|
249
|
-
read(id: string): Promise<BoardData & {
|
|
250
|
-
id: string;
|
|
251
|
-
}>;
|
|
252
|
-
/** Run `change` on the board and save it, all inside one transaction. */
|
|
253
|
-
change<R>(id: string, change: (board: Board) => R): Promise<R>;
|
|
254
|
-
add(id: string, shape: unknown, actor: Principal): Promise<Shape>;
|
|
255
|
-
connect(id: string, connector: unknown, actor: Principal): Promise<Connector>;
|
|
256
|
-
move(id: string, elementId: string, dx: number, dy: number, actor: Principal): Promise<{
|
|
257
|
-
moved: string[];
|
|
258
|
-
}>;
|
|
259
|
-
remove(id: string, elementId: string): Promise<{
|
|
260
|
-
removed: string[];
|
|
261
|
-
}>;
|
|
262
|
-
flow(id: string, text: string, actor: Principal, options?: DrawOptions): Promise<{
|
|
263
|
-
nodes: string[];
|
|
264
|
-
edges: string[];
|
|
265
|
-
}>;
|
|
266
|
-
exportSvg(id: string): Promise<string>;
|
|
267
|
-
exportPng(id: string): Promise<Uint8Array>;
|
|
268
|
-
/** Owner only. Repair a board and delete the named elements. Only a human actor may call it. */
|
|
269
|
-
repair(id: string, actor: Principal, drop?: string[]): Promise<{
|
|
270
|
-
id: string;
|
|
271
|
-
fixed: string[];
|
|
272
|
-
elements: number;
|
|
273
|
-
}>;
|
|
274
|
-
private load;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
40
|
/**
|
|
278
41
|
* The tools mirror the CLI. The actor is fixed here by the operator. No tool takes an actor,
|
|
279
42
|
* author, or kind, so a caller cannot choose who it writes as.
|
|
280
43
|
*/
|
|
281
|
-
declare function createMcpServer(service:
|
|
44
|
+
declare function createMcpServer(service: BoardAccess, actor: Principal): McpServer;
|
|
282
45
|
/** Serve the tools on stdin and stdout. Stdout carries protocol frames only. */
|
|
283
|
-
declare function runMcp(service:
|
|
46
|
+
declare function runMcp(service: BoardAccess, actor: Principal): Promise<void>;
|
|
284
47
|
|
|
285
|
-
export { ActorConfigError, BRAND, Board,
|
|
48
|
+
export { ActorConfigError, BRAND, Board, Principal, VERSION, createMcpServer, operatorActor, runMcp, svgToPng, toSvg };
|
package/dist/index.js
CHANGED
|
@@ -1,32 +1,37 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ActorConfigError,
|
|
3
3
|
BRAND,
|
|
4
|
+
VERSION,
|
|
5
|
+
createMcpServer,
|
|
6
|
+
operatorActor,
|
|
7
|
+
runMcp,
|
|
8
|
+
svgToPng
|
|
9
|
+
} from "./chunk-AGODNBN7.js";
|
|
10
|
+
import {
|
|
11
|
+
createFileStore,
|
|
12
|
+
openLocalStore
|
|
13
|
+
} from "./chunk-UKST5A57.js";
|
|
14
|
+
import {
|
|
15
|
+
CanvasService
|
|
16
|
+
} from "./chunk-W7NRP3EX.js";
|
|
17
|
+
import {
|
|
4
18
|
Board,
|
|
5
19
|
CanvasError,
|
|
6
|
-
CanvasService,
|
|
7
20
|
MAX_ELEMENTS,
|
|
8
21
|
MAX_FLOW_EDGES,
|
|
9
22
|
MAX_FLOW_NODES,
|
|
10
23
|
MAX_TEXT,
|
|
11
24
|
PRINCIPAL_KINDS,
|
|
12
25
|
SHAPE_TYPES,
|
|
13
|
-
VERSION,
|
|
14
|
-
createMcpServer,
|
|
15
26
|
drawFlow,
|
|
16
27
|
isMachineMade,
|
|
17
28
|
layoutFlow,
|
|
18
|
-
operatorActor,
|
|
19
29
|
parseFlow,
|
|
20
30
|
principalSchema,
|
|
21
31
|
readBoard,
|
|
22
|
-
runMcp,
|
|
23
|
-
svgToPng,
|
|
24
32
|
toSvg
|
|
25
|
-
} from "./chunk-
|
|
26
|
-
import
|
|
27
|
-
createFileStore,
|
|
28
|
-
openLocalStore
|
|
29
|
-
} from "./chunk-I3EJ5JB7.js";
|
|
33
|
+
} from "./chunk-GGQPE65T.js";
|
|
34
|
+
import "./chunk-NWQED42L.js";
|
|
30
35
|
export {
|
|
31
36
|
ActorConfigError,
|
|
32
37
|
BRAND,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createDurableObjectRealtime } from 'agentic-realtime/worker';
|
|
2
|
+
export { SyncRoom } from 'agentic-realtime/worker';
|
|
3
|
+
import { WorkerEnv } from './worker.js';
|
|
4
|
+
import 'agentic-identity';
|
|
5
|
+
import 'agentic-realtime';
|
|
6
|
+
import 'agentic-identity/cloudflare';
|
|
7
|
+
import 'hono';
|
|
8
|
+
import './read-BY6f-mmy.js';
|
|
9
|
+
import 'yjs';
|
|
10
|
+
import 'zod';
|
|
11
|
+
import './service-Cpl6wKeY.js';
|
|
12
|
+
|
|
13
|
+
type Namespace = Parameters<typeof createDurableObjectRealtime>[0];
|
|
14
|
+
interface LiveEnv extends WorkerEnv {
|
|
15
|
+
/** Durable Object namespace of the live rooms. Bind `SyncRoom` to it. */
|
|
16
|
+
SYNC_ROOM?: Namespace;
|
|
17
|
+
}
|
|
18
|
+
declare const _default: {
|
|
19
|
+
fetch: (request: Request, env: LiveEnv, ctx: unknown) => Response | Promise<Response>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export { type LiveEnv, _default as default };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createApp
|
|
3
|
+
} from "./chunk-V477KSZQ.js";
|
|
4
|
+
import "./chunk-W7NRP3EX.js";
|
|
5
|
+
import {
|
|
6
|
+
CanvasError
|
|
7
|
+
} from "./chunk-GGQPE65T.js";
|
|
8
|
+
import "./chunk-NWQED42L.js";
|
|
9
|
+
|
|
10
|
+
// src/live-worker.ts
|
|
11
|
+
import { parseRegion } from "agentic-identity";
|
|
12
|
+
import { createDurableObjectRealtime } from "agentic-realtime/worker";
|
|
13
|
+
import { SyncRoom } from "agentic-realtime/worker";
|
|
14
|
+
var app = createApp({
|
|
15
|
+
live: (env) => {
|
|
16
|
+
const rooms = env.SYNC_ROOM;
|
|
17
|
+
if (!rooms) throw new CanvasError("UNAVAILABLE", "bind a Durable Object namespace as SYNC_ROOM");
|
|
18
|
+
return createDurableObjectRealtime(rooms, parseRegion(env.CANVAS_REGION ?? "") === "eu" ? "eu" : void 0);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
var live_worker_default = { fetch: (request, env, ctx) => app.fetch(request, env, ctx) };
|
|
22
|
+
export {
|
|
23
|
+
SyncRoom,
|
|
24
|
+
live_worker_default as default
|
|
25
|
+
};
|
package/dist/live.d.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { c as Board, P as Principal, B as BoardData, a as Shape, C as Connector } from './read-BY6f-mmy.js';
|
|
2
|
+
import { Peer } from 'agentic-realtime/client';
|
|
3
|
+
import { B as BoardAccess, D as DrawOptions } from './service-Cpl6wKeY.js';
|
|
4
|
+
import 'yjs';
|
|
5
|
+
import 'zod';
|
|
6
|
+
import 'agentic-identity';
|
|
7
|
+
|
|
8
|
+
interface LiveOptions {
|
|
9
|
+
/** The Worker address, such as `https://canvas.example.com`. */
|
|
10
|
+
server: string;
|
|
11
|
+
/** Board id. */
|
|
12
|
+
board: string;
|
|
13
|
+
/** A bearer token. The server maps it to the actor, the kind, and the role. */
|
|
14
|
+
token: string;
|
|
15
|
+
/** The name other people see in presence. Defaults to the actor id. */
|
|
16
|
+
name?: string;
|
|
17
|
+
/** Reconnect after a drop. Edits made offline merge when the socket returns. */
|
|
18
|
+
reconnect?: {
|
|
19
|
+
initialMs?: number;
|
|
20
|
+
maxMs?: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Who the server says the token is. */
|
|
24
|
+
interface Me {
|
|
25
|
+
actor: Principal;
|
|
26
|
+
role: "editor" | "viewer";
|
|
27
|
+
}
|
|
28
|
+
declare function whoAmI(server: string, token: string): Promise<Me>;
|
|
29
|
+
/**
|
|
30
|
+
* One board, open in a live room. The actor of every edit is the one the server reads from the token, so an agent
|
|
31
|
+
* token always writes agent edits. `save` writes the room to the stored board. The server checks the stamps then.
|
|
32
|
+
*/
|
|
33
|
+
declare class LiveBoard {
|
|
34
|
+
readonly board: Board;
|
|
35
|
+
readonly me: Me;
|
|
36
|
+
private readonly client;
|
|
37
|
+
private readonly id;
|
|
38
|
+
private readonly options;
|
|
39
|
+
private readonly refusedAtStart;
|
|
40
|
+
private constructor();
|
|
41
|
+
private changed;
|
|
42
|
+
/** The ids of the elements that the last `edit` added, changed, or removed. */
|
|
43
|
+
get touched(): ReadonlySet<string>;
|
|
44
|
+
static join(options: LiveOptions): Promise<LiveBoard>;
|
|
45
|
+
/** The other people and agents in the room. `machine` is true for an agent. */
|
|
46
|
+
get peers(): Peer[];
|
|
47
|
+
/** Why the room dropped an update, in order. */
|
|
48
|
+
get denied(): string[];
|
|
49
|
+
/**
|
|
50
|
+
* Run one change as the token's actor and wait until the room has it. A viewer fails before the change.
|
|
51
|
+
* After a refusal the local copy still holds the change: leave and join again.
|
|
52
|
+
*/
|
|
53
|
+
edit<T>(run: (board: Board, actor: Principal) => T): Promise<T>;
|
|
54
|
+
/** Wait until the room has every change made so far. */
|
|
55
|
+
flush(): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Save the room to the stored board. `fixed` lists the elements the server's checks changed. After that the
|
|
58
|
+
* room still holds the client's version, so leave and join again.
|
|
59
|
+
*/
|
|
60
|
+
save(): Promise<{
|
|
61
|
+
elements: number;
|
|
62
|
+
fixed: string[];
|
|
63
|
+
}>;
|
|
64
|
+
leave(): Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The commands of the CLI and the MCP server, run in a live room. Each call joins the room, runs one change as the
|
|
68
|
+
* token's actor, saves to the stored board, and leaves. The `actor` argument is ignored: the server decides who writes.
|
|
69
|
+
* A read shows the room, which the server checks only when it saves: a forged stamp can show until then.
|
|
70
|
+
*/
|
|
71
|
+
declare class LiveService implements BoardAccess {
|
|
72
|
+
private readonly options;
|
|
73
|
+
constructor(options: Omit<LiveOptions, "board">);
|
|
74
|
+
private join;
|
|
75
|
+
create(_actor: Principal, options?: {
|
|
76
|
+
id?: string;
|
|
77
|
+
title?: string;
|
|
78
|
+
}): Promise<{
|
|
79
|
+
id: string;
|
|
80
|
+
title: string;
|
|
81
|
+
}>;
|
|
82
|
+
read(id: string): Promise<BoardData & {
|
|
83
|
+
id: string;
|
|
84
|
+
}>;
|
|
85
|
+
exportSvg(id: string): Promise<string>;
|
|
86
|
+
add(id: string, shape: unknown, _actor?: Principal): Promise<Shape>;
|
|
87
|
+
connect(id: string, connector: unknown, _actor?: Principal): Promise<Connector>;
|
|
88
|
+
move(id: string, element: string, dx: number, dy: number, _actor?: Principal): Promise<{
|
|
89
|
+
moved: string[];
|
|
90
|
+
}>;
|
|
91
|
+
remove(id: string, element: string): Promise<{
|
|
92
|
+
removed: string[];
|
|
93
|
+
}>;
|
|
94
|
+
flow(id: string, text: string, _actor?: Principal, options?: DrawOptions): Promise<{
|
|
95
|
+
nodes: string[];
|
|
96
|
+
edges: string[];
|
|
97
|
+
}>;
|
|
98
|
+
private use;
|
|
99
|
+
private write;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { LiveBoard, type LiveOptions, LiveService, type Me, whoAmI };
|
package/dist/live.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Board,
|
|
3
|
+
CanvasError,
|
|
4
|
+
drawFlow,
|
|
5
|
+
principalSchema,
|
|
6
|
+
readBoard,
|
|
7
|
+
toSvg
|
|
8
|
+
} from "./chunk-GGQPE65T.js";
|
|
9
|
+
|
|
10
|
+
// src/live.ts
|
|
11
|
+
import { SyncClient } from "agentic-realtime/client";
|
|
12
|
+
import * as Y from "yjs";
|
|
13
|
+
var CODES = ["INVALID", "DUPLICATE_ID", "NOT_FOUND", "BAD_REFERENCE", "LIMIT", "UNAVAILABLE", "FORBIDDEN"];
|
|
14
|
+
var base = (server) => server.replace(/\/$/, "") + "/api/v1/canvas";
|
|
15
|
+
async function refusal(response, fallback) {
|
|
16
|
+
const { error } = await response.json().catch(() => ({}));
|
|
17
|
+
const code = CODES.find((known) => known === error?.code) ?? (response.status === 401 || response.status === 403 ? "FORBIDDEN" : response.status >= 500 ? "UNAVAILABLE" : "INVALID");
|
|
18
|
+
return new CanvasError(code, typeof error?.message === "string" ? error.message : `${fallback} (${response.status})`);
|
|
19
|
+
}
|
|
20
|
+
async function json(response) {
|
|
21
|
+
const body = await response.json().catch(() => void 0);
|
|
22
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) throw new CanvasError("INVALID", "the server sent a body this client does not know");
|
|
23
|
+
return body;
|
|
24
|
+
}
|
|
25
|
+
var unreachable = (error) => error instanceof CanvasError ? error : new CanvasError("UNAVAILABLE", `could not reach the live server: ${error instanceof Error ? error.message : String(error)}`);
|
|
26
|
+
async function call(server, token, path, init = {}) {
|
|
27
|
+
return fetch(`${base(server)}${path}`, { ...init, headers: { ...init.headers, authorization: `Bearer ${token}` } }).catch((error) => {
|
|
28
|
+
throw unreachable(error);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async function whoAmI(server, token) {
|
|
32
|
+
const response = await call(server, token, "/me");
|
|
33
|
+
if (!response.ok) throw await refusal(response, "the server refused the token");
|
|
34
|
+
const { actor, kind, role } = await json(response);
|
|
35
|
+
const principal = principalSchema.safeParse({ kind, id: actor });
|
|
36
|
+
if (!principal.success || role !== "editor" && role !== "viewer") throw new CanvasError("INVALID", "the server sent an identity this client does not know");
|
|
37
|
+
return { actor: principal.data, role };
|
|
38
|
+
}
|
|
39
|
+
var LiveBoard = class _LiveBoard {
|
|
40
|
+
constructor(board, me, client, id, options, refusedAtStart) {
|
|
41
|
+
this.board = board;
|
|
42
|
+
this.me = me;
|
|
43
|
+
this.client = client;
|
|
44
|
+
this.id = id;
|
|
45
|
+
this.options = options;
|
|
46
|
+
this.refusedAtStart = refusedAtStart;
|
|
47
|
+
}
|
|
48
|
+
board;
|
|
49
|
+
me;
|
|
50
|
+
client;
|
|
51
|
+
id;
|
|
52
|
+
options;
|
|
53
|
+
refusedAtStart;
|
|
54
|
+
changed = /* @__PURE__ */ new Set();
|
|
55
|
+
/** The ids of the elements that the last `edit` added, changed, or removed. */
|
|
56
|
+
get touched() {
|
|
57
|
+
return this.changed;
|
|
58
|
+
}
|
|
59
|
+
static async join(options) {
|
|
60
|
+
const me = await whoAmI(options.server, options.token);
|
|
61
|
+
const id = encodeURIComponent(options.board);
|
|
62
|
+
const stored = await call(options.server, options.token, `/boards/${id}/state`);
|
|
63
|
+
if (!stored.ok) throw await refusal(stored, "could not read the stored board");
|
|
64
|
+
const state = new Uint8Array(await stored.arrayBuffer());
|
|
65
|
+
const url = new URL(`${base(options.server)}/boards/${id}/ws`);
|
|
66
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
67
|
+
let client;
|
|
68
|
+
try {
|
|
69
|
+
client = await SyncClient.connect({ url: url.toString(), headers: { authorization: `Bearer ${options.token}` }, presence: { name: options.name ?? me.actor.id }, reconnect: options.reconnect });
|
|
70
|
+
} catch (error) {
|
|
71
|
+
throw unreachable(error);
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
Y.applyUpdate(client.doc, state);
|
|
75
|
+
await client.flush();
|
|
76
|
+
if (me.role === "editor" && client.denied.length > 0) throw new CanvasError("FORBIDDEN", `the room refused the stored board: ${client.denied[0]}`);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
await client.disconnect();
|
|
79
|
+
throw unreachable(error);
|
|
80
|
+
}
|
|
81
|
+
return new _LiveBoard(new Board(client.doc), me, client, options.board, options, client.denied.length);
|
|
82
|
+
}
|
|
83
|
+
/** The other people and agents in the room. `machine` is true for an agent. */
|
|
84
|
+
get peers() {
|
|
85
|
+
return this.client.peers;
|
|
86
|
+
}
|
|
87
|
+
/** Why the room dropped an update, in order. */
|
|
88
|
+
get denied() {
|
|
89
|
+
return this.client.denied.slice(this.refusedAtStart);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Run one change as the token's actor and wait until the room has it. A viewer fails before the change.
|
|
93
|
+
* After a refusal the local copy still holds the change: leave and join again.
|
|
94
|
+
*/
|
|
95
|
+
async edit(run) {
|
|
96
|
+
if (this.me.role === "viewer") throw new CanvasError("FORBIDDEN", "this token is a viewer; it cannot change a board");
|
|
97
|
+
const seen = this.denied.length;
|
|
98
|
+
const snapshot = () => new Map(this.board.list().map((e) => [e.id, JSON.stringify(e)]));
|
|
99
|
+
const before = snapshot();
|
|
100
|
+
const result = run(this.board, this.me.actor);
|
|
101
|
+
const after = snapshot();
|
|
102
|
+
this.changed = new Set([...before.keys(), ...after.keys()].filter((id) => before.get(id) !== after.get(id)));
|
|
103
|
+
await this.flush();
|
|
104
|
+
if (this.denied.length > seen) throw new CanvasError("FORBIDDEN", `the room refused the edit: ${this.denied[seen]}`);
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
/** Wait until the room has every change made so far. */
|
|
108
|
+
flush() {
|
|
109
|
+
return this.client.flush().catch((error) => {
|
|
110
|
+
throw unreachable(error);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Save the room to the stored board. `fixed` lists the elements the server's checks changed. After that the
|
|
115
|
+
* room still holds the client's version, so leave and join again.
|
|
116
|
+
*/
|
|
117
|
+
async save() {
|
|
118
|
+
if (this.me.role === "viewer") throw new CanvasError("FORBIDDEN", "this token is a viewer; it cannot change a board");
|
|
119
|
+
await this.flush();
|
|
120
|
+
const saved = await call(this.options.server, this.options.token, `/boards/${encodeURIComponent(this.id)}/checkpoint`, { method: "POST", headers: { "content-type": "application/octet-stream" }, body: this.board.encode() });
|
|
121
|
+
if (!saved.ok) throw await refusal(saved, "the server did not save the board");
|
|
122
|
+
const { elements, fixed } = await json(saved);
|
|
123
|
+
if (typeof elements !== "number" || !Array.isArray(fixed) || !fixed.every((id) => typeof id === "string")) throw new CanvasError("INVALID", "the server sent a save result this client does not know");
|
|
124
|
+
return { elements, fixed };
|
|
125
|
+
}
|
|
126
|
+
async leave() {
|
|
127
|
+
await this.client.disconnect();
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
var LiveService = class {
|
|
131
|
+
constructor(options) {
|
|
132
|
+
this.options = options;
|
|
133
|
+
}
|
|
134
|
+
options;
|
|
135
|
+
join(board) {
|
|
136
|
+
return LiveBoard.join({ ...this.options, board });
|
|
137
|
+
}
|
|
138
|
+
async create(_actor, options = {}) {
|
|
139
|
+
const response = await call(this.options.server, this.options.token, "/boards", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(options) });
|
|
140
|
+
if (!response.ok) throw await refusal(response, "the server refused the request");
|
|
141
|
+
const { id, title } = await json(response);
|
|
142
|
+
if (typeof id !== "string" || typeof title !== "string") throw new CanvasError("INVALID", "the server sent a board this client does not know");
|
|
143
|
+
return { id, title };
|
|
144
|
+
}
|
|
145
|
+
async read(id) {
|
|
146
|
+
return this.use(id, async (live) => ({ id, ...readBoard(live.board) }));
|
|
147
|
+
}
|
|
148
|
+
async exportSvg(id) {
|
|
149
|
+
return this.use(id, async (live) => toSvg(live.board));
|
|
150
|
+
}
|
|
151
|
+
add(id, shape, _actor) {
|
|
152
|
+
return this.write(id, (board, actor) => board.addShape(shape, actor));
|
|
153
|
+
}
|
|
154
|
+
connect(id, connector, _actor) {
|
|
155
|
+
return this.write(id, (board, actor) => board.addConnector(connector, actor));
|
|
156
|
+
}
|
|
157
|
+
move(id, element, dx, dy, _actor) {
|
|
158
|
+
return this.write(id, (board, actor) => ({ moved: board.move(element, dx, dy, actor) }));
|
|
159
|
+
}
|
|
160
|
+
remove(id, element) {
|
|
161
|
+
return this.write(id, (board) => ({ removed: board.remove(element) }));
|
|
162
|
+
}
|
|
163
|
+
flow(id, text, _actor, options) {
|
|
164
|
+
return this.write(id, (board, actor) => drawFlow(board, text, actor, options));
|
|
165
|
+
}
|
|
166
|
+
async use(id, run) {
|
|
167
|
+
const live = await this.join(id);
|
|
168
|
+
try {
|
|
169
|
+
return await run(live);
|
|
170
|
+
} finally {
|
|
171
|
+
await live.leave();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
write(id, change) {
|
|
175
|
+
return this.use(id, async (live) => {
|
|
176
|
+
const result = await live.edit(change).catch((error) => {
|
|
177
|
+
if (live.touched.size === 0 || !(error instanceof CanvasError && error.code === "UNAVAILABLE")) throw error;
|
|
178
|
+
throw new CanvasError("UNAVAILABLE", `${error.message}. The room may have the change: read the board before you repeat it.`);
|
|
179
|
+
});
|
|
180
|
+
const saved = await live.save().catch((error) => {
|
|
181
|
+
throw new CanvasError(error instanceof CanvasError ? error.code : "UNAVAILABLE", `the room has the change, but it is not saved: ${error instanceof Error ? error.message : String(error)}. Do not repeat the change.`);
|
|
182
|
+
});
|
|
183
|
+
if (saved.fixed.some((id2) => live.touched.has(id2))) throw new CanvasError("FORBIDDEN", "the server undid the change: its checks changed an element this change touched. Do not repeat the change.");
|
|
184
|
+
return result;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
export {
|
|
189
|
+
LiveBoard,
|
|
190
|
+
LiveService,
|
|
191
|
+
whoAmI
|
|
192
|
+
};
|