@divlet/web 0.0.2 → 0.0.4
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/dist/board.css +2 -0
- package/dist/board.js +10 -8
- package/dist/index.d.ts +1 -1
- package/dist/lib/actions.js +27 -4
- package/dist/lib/artboards.js +5 -4
- package/dist/lib/ask.js +18 -5
- package/dist/lib/board-host.d.ts +54 -0
- package/dist/lib/canvas.js +18 -11
- package/dist/lib/clip.js +2 -1
- package/dist/lib/crumbs.js +10 -5
- package/dist/lib/help.js +3 -1
- package/dist/lib/host.js +27 -7
- package/dist/lib/http-services.js +33 -0
- package/dist/lib/inspector.js +6 -4
- package/dist/lib/mcp-provider.d.ts +70 -0
- package/dist/lib/mcp-provider.js +179 -0
- package/dist/lib/mcp-tools.d.ts +71 -0
- package/dist/lib/mcp-tools.js +277 -0
- package/dist/lib/palette.js +3 -2
- package/dist/lib/store.js +12 -9
- package/dist/lib/webmcp.d.ts +34 -0
- package/dist/lib/webmcp.js +37 -0
- package/package.json +5 -1
package/dist/lib/store.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { clamp } from './util.js';
|
|
2
|
-
import {
|
|
2
|
+
import { readSession, shareSelection, writer } from './host.js';
|
|
3
3
|
export { base, apiUrl, request, post } from './host.js';
|
|
4
4
|
|
|
5
5
|
const listeners = new Map();
|
|
@@ -20,6 +20,9 @@ export const store = {
|
|
|
20
20
|
leaving: new Set(),
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
/** A capability is unavailable only when the host says `false`; native hosts predate the newer flags and omit them. */
|
|
24
|
+
export const capable = name => store.state?.project?.capabilities?.[name] !== false;
|
|
25
|
+
|
|
23
26
|
export const documents = () => (store.state?.documents || []).filter(doc => !store.leaving.has(doc.id));
|
|
24
27
|
export const descriptor = id => documents().find(doc => doc.id === id);
|
|
25
28
|
|
|
@@ -117,10 +120,10 @@ function enqueue(documentId, task) {
|
|
|
117
120
|
return result;
|
|
118
121
|
}
|
|
119
122
|
|
|
120
|
-
async function send(
|
|
123
|
+
async function send(write, documentId, build, { version = Math.max(known.get(documentId) || 0, descriptor(documentId)?.version || 0), retry = true } = {}) {
|
|
121
124
|
for (let attempt = 0; ; attempt++) {
|
|
122
125
|
try {
|
|
123
|
-
const result = await
|
|
126
|
+
const result = await write(build(version));
|
|
124
127
|
known.set(documentId, result.version);
|
|
125
128
|
return result;
|
|
126
129
|
} catch (error) {
|
|
@@ -136,32 +139,32 @@ export function commit(documentId, patch, { label, group, expectedVersion, expec
|
|
|
136
139
|
const guard = expectedVersion === undefined ? {} : { version: expectedVersion, retry: false };
|
|
137
140
|
const format = store.state?.project?.format;
|
|
138
141
|
return enqueue(documentId, async () => {
|
|
139
|
-
if (typeof patch !== 'function') return send(
|
|
142
|
+
if (typeof patch !== 'function') return send(writer.patch, documentId, version => ({ documentId, expectedVersion: version, expectedRender, patch, label, group }), guard);
|
|
140
143
|
if (store.state?.project?.format !== format) throw Object.assign(new Error('The source format changed. Read it again before retrying.'), { code: 'REVISION_CONFLICT' });
|
|
141
144
|
const model = await freshModel(documentId);
|
|
142
145
|
if (store.state?.project?.format !== format || model !== cachedModel(documentId) || model.version < (known.get(documentId) || 0)) throw Object.assign(new Error('The frame model is not current. Read it again before retrying.'), { code: 'REVISION_CONFLICT' });
|
|
143
146
|
const derived = await patch(model);
|
|
144
147
|
if (store.state?.project?.format !== format || model !== cachedModel(documentId)) throw Object.assign(new Error('The frame changed while preparing this edit. Read it again before retrying.'), { code: 'REVISION_CONFLICT' });
|
|
145
148
|
if (!derived) return null;
|
|
146
|
-
return send(
|
|
149
|
+
return send(writer.patch, documentId, version => ({ documentId, expectedVersion: version, expectedRender, patch: derived, label, group }), { version: expectedVersion ?? model.version, retry: false });
|
|
147
150
|
}).catch(error => { toast(error.message); throw error; });
|
|
148
151
|
}
|
|
149
152
|
export function edit(documentId, op, { label, group, expectedRender } = {}) {
|
|
150
153
|
const format = store.state?.project?.format;
|
|
151
154
|
return enqueue(documentId, async () => {
|
|
152
|
-
if (typeof op !== 'function') return send(
|
|
155
|
+
if (typeof op !== 'function') return send(writer.edit, documentId, expectedVersion => ({ documentId, expectedVersion, expectedRender, op, label, group }));
|
|
153
156
|
// Derive relative legacy intent after local commits; native operations keep their shown-view guards.
|
|
154
157
|
if (store.state?.project?.format !== format) throw Object.assign(new Error('The source format changed. Read it again before retrying.'), { code: 'REVISION_CONFLICT' });
|
|
155
158
|
const model = await freshModel(documentId);
|
|
156
159
|
if (store.state?.project?.format !== format || model !== cachedModel(documentId) || model.version < (known.get(documentId) || 0)) throw Object.assign(new Error('The frame model is not current. Read it again before retrying.'), { code: 'REVISION_CONFLICT' });
|
|
157
160
|
const derived = op(model);
|
|
158
161
|
if (!derived) return null;
|
|
159
|
-
return send(
|
|
162
|
+
return send(writer.edit, documentId, expectedVersion => ({ documentId, expectedVersion, expectedRender, op: derived, label, group }), { version: model.version, retry: false });
|
|
160
163
|
}).catch(error => { toast(error.message); throw error; });
|
|
161
164
|
}
|
|
162
165
|
|
|
163
|
-
export const undo = () =>
|
|
164
|
-
export const redo = () =>
|
|
166
|
+
export const undo = () => writer.undo({}).catch(error => { toast(error.message); return null; });
|
|
167
|
+
export const redo = () => writer.redo({}).catch(error => { toast(error.message); return null; });
|
|
165
168
|
|
|
166
169
|
/* ---- selection ------------------------------------------------------------ */
|
|
167
170
|
let selectionSequence = 0;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ToolRegistry } from './mcp-tools.ts';
|
|
2
|
+
interface WebMcpToolInit {
|
|
3
|
+
name: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema?: object;
|
|
7
|
+
annotations?: {
|
|
8
|
+
readOnlyHint?: boolean;
|
|
9
|
+
consequentialHint?: boolean;
|
|
10
|
+
};
|
|
11
|
+
execute(input: object, options: {
|
|
12
|
+
signal: AbortSignal;
|
|
13
|
+
}): Promise<unknown>;
|
|
14
|
+
}
|
|
15
|
+
/** The part of the draft's `ModelContext` this adapter uses. */
|
|
16
|
+
export interface WebMcpModelContext {
|
|
17
|
+
registerTool(tool: WebMcpToolInit, options?: {
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
}): Promise<unknown> | unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Register every tool of `registry` with WebMCP. Resolves to the names that registered (an empty list when the browser has no
|
|
23
|
+
* `document.modelContext`, for example outside a secure context) and a function that unregisters them. Failures of individual
|
|
24
|
+
* registrations are reported, never thrown: WebMCP is a compatibility surface, not a requirement.
|
|
25
|
+
*/
|
|
26
|
+
export declare function registerWebMcpTools(registry: ToolRegistry, context?: unknown): Promise<{
|
|
27
|
+
registered: string[];
|
|
28
|
+
failed: Array<{
|
|
29
|
+
name: string;
|
|
30
|
+
message: string;
|
|
31
|
+
}>;
|
|
32
|
+
unregister: () => void;
|
|
33
|
+
}>;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const isModelContext = (value) => !!value && typeof value.registerTool === 'function';
|
|
2
|
+
/**
|
|
3
|
+
* Register every tool of `registry` with WebMCP. Resolves to the names that registered (an empty list when the browser has no
|
|
4
|
+
* `document.modelContext`, for example outside a secure context) and a function that unregisters them. Failures of individual
|
|
5
|
+
* registrations are reported, never thrown: WebMCP is a compatibility surface, not a requirement.
|
|
6
|
+
*/
|
|
7
|
+
export async function registerWebMcpTools(registry, context = globalThis.document?.modelContext) {
|
|
8
|
+
const controller = new AbortController();
|
|
9
|
+
const unregister = () => controller.abort();
|
|
10
|
+
if (!isModelContext(context))
|
|
11
|
+
return { registered: [], failed: [], unregister };
|
|
12
|
+
const registered = [], failed = [];
|
|
13
|
+
for (const descriptor of registry.descriptors) {
|
|
14
|
+
const readOnly = descriptor.annotations?.readOnlyHint === true;
|
|
15
|
+
const init = {
|
|
16
|
+
name: descriptor.name,
|
|
17
|
+
...(descriptor.title ? { title: descriptor.title } : {}),
|
|
18
|
+
description: descriptor.description,
|
|
19
|
+
inputSchema: descriptor.inputSchema,
|
|
20
|
+
annotations: { readOnlyHint: readOnly, consequentialHint: !readOnly && descriptor.annotations?.destructiveHint !== false },
|
|
21
|
+
async execute(input, options) {
|
|
22
|
+
const outcome = await registry.run(descriptor.name, input, { signal: options?.signal });
|
|
23
|
+
if (outcome.ok)
|
|
24
|
+
return outcome.value;
|
|
25
|
+
throw Object.assign(new Error(`${outcome.error.code}: ${outcome.error.message}`), { code: outcome.error.code });
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
try {
|
|
29
|
+
await context.registerTool(init, { signal: controller.signal });
|
|
30
|
+
registered.push(descriptor.name);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
failed.push({ name: descriptor.name, message: String(error?.message || error) });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { registered, failed, unregister };
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@divlet/web",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "The divlet browser board, static client assets and transport-free host ports",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"./host": {
|
|
25
25
|
"divlet-source": "./lib/board-host.ts",
|
|
26
26
|
"default": "./dist/lib/board-host.js"
|
|
27
|
+
},
|
|
28
|
+
"./mcp": {
|
|
29
|
+
"divlet-source": "./lib/mcp-provider.ts",
|
|
30
|
+
"default": "./dist/lib/mcp-provider.js"
|
|
27
31
|
}
|
|
28
32
|
},
|
|
29
33
|
"files": [
|