@guowenzhang/dsh-whiteboard 0.2.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/lib/index.mjs ADDED
@@ -0,0 +1,241 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { lstat, mkdir, readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
4
+ import { setSandboxMode } from "@deepseek-ai/dsh-sandbox-policy";
5
+ import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
6
+ //#region src/board-name.ts
7
+ /** Validate a single filename stem. The extension is managed by the plugin. */
8
+ function normalizeBoardName(value) {
9
+ if (typeof value !== "string") throw new Error("WHITEBOARD_INVALID_NAME");
10
+ const name = value.trim().replace(/\.drawio$/iu, "");
11
+ if (!name || name.length > 100 || /[<>:"/\\|?*\x00-\x1f]/u.test(name) || /[. ]$/u.test(name) || /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/iu.test(name)) throw new Error("WHITEBOARD_INVALID_NAME");
12
+ return name;
13
+ }
14
+ //#endregion
15
+ //#region src/boards.ts
16
+ const BLANK_BOARD = "<mxGraphModel dx=\"900\" dy=\"600\" grid=\"1\" gridSize=\"10\" guides=\"1\" tooltips=\"1\" connect=\"1\" arrows=\"1\" fold=\"1\" page=\"1\" pageScale=\"1\" pageWidth=\"850\" pageHeight=\"1100\"><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/></root></mxGraphModel>";
17
+ function titleOf(name) {
18
+ return name.slice(0, -7);
19
+ }
20
+ function boardId(value) {
21
+ if (typeof value !== "string" || !value.toLowerCase().endsWith(".drawio") || value.length <= 7 || value.length > 255 || /[<>:"/\\|?*\x00-\x1f]/u.test(value)) throw new Error("WHITEBOARD_INVALID_ID");
22
+ return value;
23
+ }
24
+ var BoardStore = class {
25
+ directory;
26
+ constructor(directory) {
27
+ this.directory = directory;
28
+ }
29
+ async list() {
30
+ await mkdir(this.directory, { recursive: true });
31
+ const files = (await readdir(this.directory, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".drawio"));
32
+ return (await Promise.all(files.map(async (entry) => {
33
+ const info = await stat(join(this.directory, entry.name));
34
+ let title = titleOf(entry.name);
35
+ if (/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}\.drawio$/iu.test(entry.name)) try {
36
+ const metadata = JSON.parse(await readFile(join(this.directory, `${title}.json`), "utf8"));
37
+ if (typeof metadata === "object" && metadata !== null && typeof metadata.title === "string") title = metadata.title;
38
+ } catch {}
39
+ return {
40
+ id: entry.name,
41
+ title,
42
+ path: join(this.directory, entry.name),
43
+ directory: this.directory,
44
+ updatedAt: info.mtimeMs
45
+ };
46
+ }))).sort((a, b) => b.updatedAt - a.updatedAt || a.id.localeCompare(b.id));
47
+ }
48
+ async create(value) {
49
+ await mkdir(this.directory, { recursive: true });
50
+ const name = `${normalizeBoardName(value)}.drawio`;
51
+ const path = join(this.directory, name);
52
+ try {
53
+ await writeFile(path, BLANK_BOARD, { flag: "wx" });
54
+ } catch (error) {
55
+ if (error.code === "EEXIST") throw new Error("WHITEBOARD_NAME_EXISTS");
56
+ throw error;
57
+ }
58
+ const info = await stat(path);
59
+ return {
60
+ id: name,
61
+ title: titleOf(name),
62
+ path,
63
+ directory: this.directory,
64
+ updatedAt: info.mtimeMs
65
+ };
66
+ }
67
+ async preview(value) {
68
+ const path = join(this.directory, boardId(value));
69
+ const info = await lstat(path);
70
+ if (!info.isFile()) throw new Error("WHITEBOARD_INVALID_ID");
71
+ if (info.size > 4194304) return null;
72
+ return readFile(path, "utf8");
73
+ }
74
+ async delete(value, expectedUpdatedAt) {
75
+ const id = boardId(value);
76
+ const path = join(this.directory, id);
77
+ const info = await lstat(path);
78
+ if (!info.isFile()) throw new Error("WHITEBOARD_INVALID_ID");
79
+ if (typeof expectedUpdatedAt !== "number" || info.mtimeMs !== expectedUpdatedAt) throw new Error("WHITEBOARD_MODIFIED");
80
+ await unlink(path);
81
+ if (/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}\.drawio$/iu.test(id)) try {
82
+ await unlink(join(this.directory, `${titleOf(id)}.json`));
83
+ } catch (error) {
84
+ if (error.code !== "ENOENT") throw error;
85
+ }
86
+ }
87
+ };
88
+ //#endregion
89
+ //#region src/board-service.ts
90
+ var __runInitializers = function(thisArg, initializers, value) {
91
+ var useValue = arguments.length > 2;
92
+ for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
93
+ return useValue ? value : void 0;
94
+ };
95
+ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
96
+ function accept(f) {
97
+ if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
98
+ return f;
99
+ }
100
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
101
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
102
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
103
+ var _, done = false;
104
+ for (var i = decorators.length - 1; i >= 0; i--) {
105
+ var context = {};
106
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
107
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
108
+ context.addInitializer = function(f) {
109
+ if (done) throw new TypeError("Cannot add initializers after decoration has completed");
110
+ extraInitializers.push(accept(f || null));
111
+ };
112
+ var result = (0, decorators[i])(kind === "accessor" ? {
113
+ get: descriptor.get,
114
+ set: descriptor.set
115
+ } : descriptor[key], context);
116
+ if (kind === "accessor") {
117
+ if (result === void 0) continue;
118
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
119
+ if (_ = accept(result.get)) descriptor.get = _;
120
+ if (_ = accept(result.set)) descriptor.set = _;
121
+ if (_ = accept(result.init)) initializers.unshift(_);
122
+ } else if (_ = accept(result)) {
123
+ if (kind === "field") initializers.unshift(_);
124
+ else descriptor[key] = _;
125
+ }
126
+ }
127
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
128
+ done = true;
129
+ };
130
+ let BoardService = (() => {
131
+ let _classSuper = TypertRemoteService;
132
+ let _instanceExtraInitializers = [];
133
+ let _listBoards_decorators;
134
+ let _createBoard_decorators;
135
+ let _previewBoard_decorators;
136
+ let _deleteBoard_decorators;
137
+ let _grantBoardSession_decorators;
138
+ return class BoardService extends _classSuper {
139
+ static {
140
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
141
+ _listBoards_decorators = [Remote("listBoards")];
142
+ _createBoard_decorators = [Remote("createBoard")];
143
+ _previewBoard_decorators = [Remote("previewBoard")];
144
+ _deleteBoard_decorators = [Remote("deleteBoard")];
145
+ _grantBoardSession_decorators = [Remote("grantBoardSession")];
146
+ __esDecorate(this, null, _listBoards_decorators, {
147
+ kind: "method",
148
+ name: "listBoards",
149
+ static: false,
150
+ private: false,
151
+ access: {
152
+ has: (obj) => "listBoards" in obj,
153
+ get: (obj) => obj.listBoards
154
+ },
155
+ metadata: _metadata
156
+ }, null, _instanceExtraInitializers);
157
+ __esDecorate(this, null, _createBoard_decorators, {
158
+ kind: "method",
159
+ name: "createBoard",
160
+ static: false,
161
+ private: false,
162
+ access: {
163
+ has: (obj) => "createBoard" in obj,
164
+ get: (obj) => obj.createBoard
165
+ },
166
+ metadata: _metadata
167
+ }, null, _instanceExtraInitializers);
168
+ __esDecorate(this, null, _previewBoard_decorators, {
169
+ kind: "method",
170
+ name: "previewBoard",
171
+ static: false,
172
+ private: false,
173
+ access: {
174
+ has: (obj) => "previewBoard" in obj,
175
+ get: (obj) => obj.previewBoard
176
+ },
177
+ metadata: _metadata
178
+ }, null, _instanceExtraInitializers);
179
+ __esDecorate(this, null, _deleteBoard_decorators, {
180
+ kind: "method",
181
+ name: "deleteBoard",
182
+ static: false,
183
+ private: false,
184
+ access: {
185
+ has: (obj) => "deleteBoard" in obj,
186
+ get: (obj) => obj.deleteBoard
187
+ },
188
+ metadata: _metadata
189
+ }, null, _instanceExtraInitializers);
190
+ __esDecorate(this, null, _grantBoardSession_decorators, {
191
+ kind: "method",
192
+ name: "grantBoardSession",
193
+ static: false,
194
+ private: false,
195
+ access: {
196
+ has: (obj) => "grantBoardSession" in obj,
197
+ get: (obj) => obj.grantBoardSession
198
+ },
199
+ metadata: _metadata
200
+ }, null, _instanceExtraInitializers);
201
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, {
202
+ enumerable: true,
203
+ configurable: true,
204
+ writable: true,
205
+ value: _metadata
206
+ });
207
+ }
208
+ store = __runInitializers(this, _instanceExtraInitializers);
209
+ constructor(ctx, store) {
210
+ super(ctx, "whiteboard");
211
+ this.store = store;
212
+ }
213
+ async listBoards() {
214
+ return this.store.list();
215
+ }
216
+ async createBoard(request) {
217
+ return this.store.create(request?.name);
218
+ }
219
+ async previewBoard(request) {
220
+ return this.store.preview(request?.id);
221
+ }
222
+ async deleteBoard(request) {
223
+ return this.store.delete(request?.id, request?.expectedUpdatedAt);
224
+ }
225
+ async grantBoardSession(request) {
226
+ const session = typeof request?.sessionId === "string" ? this.ctx.sessions.get(request.sessionId) : void 0;
227
+ const expected = resolve(this.store.directory).toLowerCase();
228
+ if (!session || typeof session.header.cwd !== "string" || resolve(session.header.cwd).toLowerCase() !== expected) throw new Error("Whiteboard session must belong to the plugin files directory");
229
+ setSandboxMode(session, "workspace-write");
230
+ }
231
+ };
232
+ })();
233
+ //#endregion
234
+ //#region src/index.ts
235
+ const name = "whiteboard";
236
+ const inject = ["sessions"];
237
+ function apply(ctx) {
238
+ new BoardService(ctx, new BoardStore(fileURLToPath(new URL("../files/", import.meta.url))));
239
+ }
240
+ //#endregion
241
+ export { apply, inject, name };
package/package.json ADDED
@@ -0,0 +1,99 @@
1
+ {
2
+ "name": "@guowenzhang/dsh-whiteboard",
3
+ "version": "0.2.0",
4
+ "description": "Plugin-owned whiteboards with a card gallery and fullscreen draw.io editing for DeepSeek Harness",
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "main": "lib/index.mjs",
8
+ "exports": {
9
+ ".": "./lib/index.mjs",
10
+ "./client": "./lib/client.js",
11
+ "./package.json": "./package.json",
12
+ "./cordis.patch.yml": "./cordis.patch.yml"
13
+ },
14
+ "engines": { "node": "^22.19.0 || >=24.0.0", "dsh": ">=0.2.0-rc.1" },
15
+ "dsh": {
16
+ "bundle": { "patch": "./cordis.patch.yml" },
17
+ "client": {
18
+ "inject": [
19
+ "@deepseek-ai/dsh-api-remotes",
20
+ "@deepseek-ai/dsh-api-session-controller",
21
+ "@deepseek-ai/dsh-client-locale",
22
+ "@deepseek-ai/dsh-client-ui-renderer",
23
+ "@deepseek-ai/dsh-client-ui-slots",
24
+ "@deepseek-ai/dsh-client-ui-layout",
25
+ "@deepseek-ai/dsh-client-ui-sidebar",
26
+ "@deepseek-ai/dsh-client-ui-sidebar-right",
27
+ "@deepseek-ai/dsh-client-ui-workspace"
28
+ ],
29
+ "platform": "web"
30
+ }
31
+ },
32
+ "files": ["lib", "files/README.md", "cordis.patch.yml", "README.md", "README.zh.md", "LICENSE", "NOTICE"],
33
+ "scripts": {
34
+ "build": "tsdown -c tsdown.config.ts && node build-client.mjs",
35
+ "typecheck": "tsc --noEmit -p tsconfig.json",
36
+ "test": "vitest run"
37
+ },
38
+ "peerDependencies": {
39
+ "@guowenzhang/dsh-drawioedit": ">=1.0.0",
40
+ "@deepseek-ai/cordis": "*",
41
+ "@deepseek-ai/dsh-api-remotes": ">=0.2.0-rc.1",
42
+ "@deepseek-ai/dsh-api-session-controller": ">=0.2.0-rc.1",
43
+ "@deepseek-ai/dsh-client-locale": ">=0.2.0-rc.1",
44
+ "@deepseek-ai/dsh-client-ui-renderer": ">=0.2.0-rc.1",
45
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.2.0-rc.1",
46
+ "@deepseek-ai/dsh-client-ui-layout": ">=0.2.0-rc.1",
47
+ "@deepseek-ai/dsh-client-ui-sidebar": ">=0.2.0-rc.1",
48
+ "@deepseek-ai/dsh-client-ui-sidebar-right": ">=0.2.0-rc.1",
49
+ "@deepseek-ai/dsh-client-ui-workspace": ">=0.2.0-rc.1",
50
+ "@deepseek-ai/dsh-sandbox-policy": ">=0.2.0-rc.1",
51
+ "@deepseek-ai/dsh-session": ">=0.2.0-rc.1",
52
+ "@deepseek-ai/dsh-util-workspace-path": ">=0.2.0-rc.1",
53
+ "@deepseek-ai/dsh-typert-protocol": ">=0.2.0-rc.1",
54
+ "react": "*"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "@deepseek-ai/cordis": { "optional": true },
58
+ "@deepseek-ai/dsh-api-remotes": { "optional": true },
59
+ "@deepseek-ai/dsh-api-session-controller": { "optional": true },
60
+ "@deepseek-ai/dsh-client-locale": { "optional": true },
61
+ "@deepseek-ai/dsh-client-ui-renderer": { "optional": true },
62
+ "@deepseek-ai/dsh-client-ui-slots": { "optional": true },
63
+ "@deepseek-ai/dsh-client-ui-layout": { "optional": true },
64
+ "@deepseek-ai/dsh-client-ui-sidebar": { "optional": true },
65
+ "@deepseek-ai/dsh-client-ui-sidebar-right": { "optional": true },
66
+ "@deepseek-ai/dsh-client-ui-workspace": { "optional": true },
67
+ "@deepseek-ai/dsh-sandbox-policy": { "optional": true },
68
+ "@deepseek-ai/dsh-session": { "optional": true },
69
+ "@deepseek-ai/dsh-util-workspace-path": { "optional": true },
70
+ "@deepseek-ai/dsh-typert-protocol": { "optional": true },
71
+ "react": { "optional": true }
72
+ },
73
+ "devDependencies": {
74
+ "@deepseek-ai/cordis": "^4.0.4",
75
+ "@deepseek-ai/dsh-api-remotes": "0.2.0-rc.1",
76
+ "@deepseek-ai/dsh-api-session-controller": "0.2.0-rc.1",
77
+ "@deepseek-ai/dsh-client-locale": "0.2.0-rc.1",
78
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.1",
79
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.1",
80
+ "@deepseek-ai/dsh-client-ui-layout": "0.2.0-rc.1",
81
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.2.0-rc.1",
82
+ "@deepseek-ai/dsh-client-ui-sidebar-right": "0.2.0-rc.1",
83
+ "@deepseek-ai/dsh-client-ui-workspace": "0.2.0-rc.1",
84
+ "@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.1",
85
+ "@deepseek-ai/dsh-session": "0.2.0-rc.1",
86
+ "@deepseek-ai/dsh-util-workspace-path": "0.2.0-rc.1",
87
+ "@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.1",
88
+ "@maxgraph/core": "^0.24.0",
89
+ "@types/node": "^26.5.0",
90
+ "@types/react": "~18.3.1",
91
+ "fflate": "^0.8.3",
92
+ "lightningcss": "^1.32.0",
93
+ "react": "^18.3.1",
94
+ "rolldown": "^1.0.0",
95
+ "tsdown": "^0.22.2",
96
+ "typescript": "^6.0.3",
97
+ "vitest": "^3.2.4"
98
+ }
99
+ }