@particle-academy/agent-integrations 0.6.3 → 0.7.1
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/bridges/artboard.d.cts +78 -0
- package/dist/bridges/artboard.d.ts +78 -0
- package/dist/bridges/slides.d.cts +1 -0
- package/dist/bridges/slides.d.ts +1 -0
- package/dist/bridges-artboard.cjs +576 -0
- package/dist/bridges-artboard.cjs.map +1 -0
- package/dist/bridges-artboard.js +418 -0
- package/dist/bridges-artboard.js.map +1 -0
- package/dist/bridges-slides.cjs +19 -0
- package/dist/bridges-slides.cjs.map +1 -1
- package/dist/bridges-slides.js +1 -1
- package/dist/{chunk-NE3GIGQD.js → chunk-VWWATPRJ.js} +21 -2
- package/dist/chunk-VWWATPRJ.js.map +1 -0
- package/dist/index.cjs +19 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/package.json +9 -3
- package/dist/chunk-NE3GIGQD.js.map +0 -1
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/mcp/server.ts
|
|
4
|
+
function textResult(text, structured) {
|
|
5
|
+
return {
|
|
6
|
+
content: [{ type: "text", text }],
|
|
7
|
+
...structured !== void 0 ? { structuredContent: structured } : {}
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function errorResult(text) {
|
|
11
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
12
|
+
}
|
|
13
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
14
|
+
function emitActivity(event) {
|
|
15
|
+
for (const l of listeners) l(event);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/presence/wrap-tool-with-activity.ts
|
|
19
|
+
function wrapToolWithActivity(handler, options) {
|
|
20
|
+
return async (args) => {
|
|
21
|
+
const result = await handler(args);
|
|
22
|
+
if (result.isError) return result;
|
|
23
|
+
let target;
|
|
24
|
+
if (options.resolveTarget) {
|
|
25
|
+
target = options.resolveTarget({ toolName: options.toolName, args, result });
|
|
26
|
+
} else {
|
|
27
|
+
target = { kind: options.kind, screenId: options.screenId };
|
|
28
|
+
}
|
|
29
|
+
if (!target) return result;
|
|
30
|
+
emitActivity({
|
|
31
|
+
agentId: options.agent.id,
|
|
32
|
+
agentName: options.agent.name,
|
|
33
|
+
agentColor: options.agent.color,
|
|
34
|
+
target: { ...target, kind: target.kind ?? options.kind, screenId: target.screenId ?? options.screenId },
|
|
35
|
+
action: options.toolName,
|
|
36
|
+
timestamp: Date.now(),
|
|
37
|
+
meta: extractMeta(result),
|
|
38
|
+
ttlMs: options.ttlMs
|
|
39
|
+
});
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function extractMeta(result) {
|
|
44
|
+
const sc = result.structuredContent;
|
|
45
|
+
if (sc && typeof sc === "object" && !Array.isArray(sc)) {
|
|
46
|
+
return sc;
|
|
47
|
+
}
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/undo/undo-stack.ts
|
|
52
|
+
var stacks = /* @__PURE__ */ new Map();
|
|
53
|
+
var CAP = 200;
|
|
54
|
+
function getStack(agentId) {
|
|
55
|
+
let s = stacks.get(agentId);
|
|
56
|
+
if (!s) {
|
|
57
|
+
s = { past: [], future: [] };
|
|
58
|
+
stacks.set(agentId, s);
|
|
59
|
+
}
|
|
60
|
+
return s;
|
|
61
|
+
}
|
|
62
|
+
function pushUndoEntry(agentId, entry) {
|
|
63
|
+
const s = getStack(agentId);
|
|
64
|
+
s.past.push(entry);
|
|
65
|
+
if (s.past.length > CAP) s.past.splice(0, s.past.length - CAP);
|
|
66
|
+
s.future.length = 0;
|
|
67
|
+
}
|
|
68
|
+
async function undoOne(agentId) {
|
|
69
|
+
const s = getStack(agentId);
|
|
70
|
+
const entry = s.past.pop();
|
|
71
|
+
if (!entry) return null;
|
|
72
|
+
await entry.undo();
|
|
73
|
+
s.future.push(entry);
|
|
74
|
+
return entry;
|
|
75
|
+
}
|
|
76
|
+
async function redoOne(agentId) {
|
|
77
|
+
const s = getStack(agentId);
|
|
78
|
+
const entry = s.future.pop();
|
|
79
|
+
if (!entry) return null;
|
|
80
|
+
await entry.redo();
|
|
81
|
+
s.past.push(entry);
|
|
82
|
+
return entry;
|
|
83
|
+
}
|
|
84
|
+
function readHistory(agentId) {
|
|
85
|
+
return getStack(agentId).past.slice();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/undo/undo-tools.ts
|
|
89
|
+
var installedHosts = /* @__PURE__ */ new WeakSet();
|
|
90
|
+
function ensureUndoToolsRegistered(host, options = {}) {
|
|
91
|
+
if (installedHosts.has(host)) return;
|
|
92
|
+
installedHosts.add(host);
|
|
93
|
+
registerUndoTools(host, options);
|
|
94
|
+
}
|
|
95
|
+
function registerUndoTools(host, options = {}) {
|
|
96
|
+
const defaultAgent = options.defaultAgentId ?? "agent";
|
|
97
|
+
const disposers = [];
|
|
98
|
+
const agentOf = (args) => typeof args?.agentId === "string" ? args.agentId : defaultAgent;
|
|
99
|
+
disposers.push(
|
|
100
|
+
host.registerTool(
|
|
101
|
+
{
|
|
102
|
+
name: "agent_undo",
|
|
103
|
+
description: "Undo the most recent action on the agent's stack. Optional agentId targets a specific agent.",
|
|
104
|
+
inputSchema: {
|
|
105
|
+
type: "object",
|
|
106
|
+
properties: { agentId: { type: "string" } },
|
|
107
|
+
additionalProperties: false
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
async (args) => {
|
|
111
|
+
const entry = await undoOne(agentOf(args));
|
|
112
|
+
if (!entry) return errorResult("Nothing to undo.");
|
|
113
|
+
return textResult(`Undid: ${entry.label}`, { entry: serialize(entry) });
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
);
|
|
117
|
+
disposers.push(
|
|
118
|
+
host.registerTool(
|
|
119
|
+
{
|
|
120
|
+
name: "agent_redo",
|
|
121
|
+
description: "Redo the most recently undone action.",
|
|
122
|
+
inputSchema: {
|
|
123
|
+
type: "object",
|
|
124
|
+
properties: { agentId: { type: "string" } },
|
|
125
|
+
additionalProperties: false
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
async (args) => {
|
|
129
|
+
const entry = await redoOne(agentOf(args));
|
|
130
|
+
if (!entry) return errorResult("Nothing to redo.");
|
|
131
|
+
return textResult(`Redid: ${entry.label}`, { entry: serialize(entry) });
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
);
|
|
135
|
+
disposers.push(
|
|
136
|
+
host.registerTool(
|
|
137
|
+
{
|
|
138
|
+
name: "agent_history",
|
|
139
|
+
description: "List the agent's undo stack (oldest first). Useful for understanding what's reversible.",
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: { agentId: { type: "string" } },
|
|
143
|
+
additionalProperties: false
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
async (args) => {
|
|
147
|
+
const history2 = readHistory(agentOf(args)).map(serialize);
|
|
148
|
+
const text = history2.map((e) => `${new Date(e.timestamp).toISOString()} ${e.bridgeId} ${e.action}: ${e.label}`).join("\n");
|
|
149
|
+
return textResult(text || "(empty)", history2);
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
);
|
|
153
|
+
return () => disposers.forEach((d) => d());
|
|
154
|
+
}
|
|
155
|
+
function serialize(entry) {
|
|
156
|
+
return {
|
|
157
|
+
timestamp: entry.timestamp,
|
|
158
|
+
bridgeId: entry.bridgeId,
|
|
159
|
+
action: entry.action,
|
|
160
|
+
label: entry.label
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/bridges/artboard.ts
|
|
165
|
+
var DEFAULT_AGENT = { id: "agent", name: "Agent", color: "#a855f7" };
|
|
166
|
+
var num = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? v : fallback ?? 0;
|
|
167
|
+
var str = (v, fallback = "") => typeof v === "string" ? v : fallback;
|
|
168
|
+
var newId = (prefix) => `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
|
|
169
|
+
var clone = (v) => JSON.parse(JSON.stringify(v));
|
|
170
|
+
var coerceContent = (v) => {
|
|
171
|
+
if (v && typeof v === "object") {
|
|
172
|
+
const c = v;
|
|
173
|
+
if (c.kind === "image") return { kind: "image", src: str(c.src), ...c.alt !== void 0 ? { alt: str(c.alt) } : {} };
|
|
174
|
+
if (c.kind === "html") return { kind: "html", html: str(c.html) };
|
|
175
|
+
if (c.kind === "node") return { kind: "node" };
|
|
176
|
+
}
|
|
177
|
+
return { kind: "node" };
|
|
178
|
+
};
|
|
179
|
+
function registerArtboardBridge(host, options) {
|
|
180
|
+
const { adapter } = options;
|
|
181
|
+
const agent = { ...DEFAULT_AGENT, ...options.agent ?? {} };
|
|
182
|
+
const disposers = [];
|
|
183
|
+
ensureUndoToolsRegistered(host, { defaultAgentId: agent.id });
|
|
184
|
+
const abTarget = (args, result) => ({
|
|
185
|
+
kind: "artboard",
|
|
186
|
+
id: result?.structuredContent?.id ?? args?.pieceId ?? args?.sectionId
|
|
187
|
+
});
|
|
188
|
+
const reg = (name, description, inputProperties, required, handler, resolveTarget) => {
|
|
189
|
+
const wrapped = async (args) => {
|
|
190
|
+
try {
|
|
191
|
+
return await handler(args);
|
|
192
|
+
} catch (e) {
|
|
193
|
+
return errorResult(e instanceof Error ? e.message : String(e));
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const final = resolveTarget ? wrapToolWithActivity(wrapped, {
|
|
197
|
+
toolName: name,
|
|
198
|
+
agent: { id: agent.id, name: agent.name, color: agent.color },
|
|
199
|
+
kind: "artboard",
|
|
200
|
+
resolveTarget: ({ args, result }) => resolveTarget(args, result)
|
|
201
|
+
}) : wrapped;
|
|
202
|
+
disposers.push(
|
|
203
|
+
host.registerTool(
|
|
204
|
+
{
|
|
205
|
+
name,
|
|
206
|
+
description,
|
|
207
|
+
inputSchema: {
|
|
208
|
+
type: "object",
|
|
209
|
+
properties: inputProperties,
|
|
210
|
+
required,
|
|
211
|
+
additionalProperties: false
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
final
|
|
215
|
+
)
|
|
216
|
+
);
|
|
217
|
+
};
|
|
218
|
+
reg("artboard_get_state", "Get the full board state: viewport, focus, all sections and pieces.", {}, [], () => {
|
|
219
|
+
const state = {
|
|
220
|
+
viewport: adapter.getViewport(),
|
|
221
|
+
focus: adapter.getFocus(),
|
|
222
|
+
sections: adapter.getValue().sections
|
|
223
|
+
};
|
|
224
|
+
return textResult(JSON.stringify(state, null, 2), state);
|
|
225
|
+
});
|
|
226
|
+
reg("artboard_list_pieces", "List sections and their pieces with id, label, and content kind.", {}, [], () => {
|
|
227
|
+
const lines = [];
|
|
228
|
+
const summary = [];
|
|
229
|
+
for (const s of adapter.getValue().sections) {
|
|
230
|
+
lines.push(`section ${s.id}: "${s.title}"`);
|
|
231
|
+
const pieces = s.pieces.map((p) => ({
|
|
232
|
+
id: p.id,
|
|
233
|
+
...p.label !== void 0 ? { label: p.label } : {},
|
|
234
|
+
kind: p.content.kind,
|
|
235
|
+
...p.pending ? { pending: true } : {}
|
|
236
|
+
}));
|
|
237
|
+
for (const p of s.pieces) {
|
|
238
|
+
lines.push(` piece ${p.id}: ${p.label ? `"${p.label}" ` : ""}[${p.content.kind}]${p.pending ? " (pending)" : ""}`);
|
|
239
|
+
}
|
|
240
|
+
summary.push({ sectionId: s.id, title: s.title, pieces });
|
|
241
|
+
}
|
|
242
|
+
return textResult(lines.join("\n") || "(empty board)", summary);
|
|
243
|
+
});
|
|
244
|
+
reg(
|
|
245
|
+
"artboard_add_piece",
|
|
246
|
+
"Add a piece to a section. New agent-added pieces default to pending:true (trust-but-verify \u2014 a human confirms before it is final).",
|
|
247
|
+
{
|
|
248
|
+
sectionId: { type: "string" },
|
|
249
|
+
piece: {
|
|
250
|
+
type: "object",
|
|
251
|
+
description: "Piece spec: { label?, width?, height?, content }. content is { kind: 'image', src, alt? } | { kind: 'html', html } | { kind: 'node' }."
|
|
252
|
+
},
|
|
253
|
+
index: { type: "number", description: "Insertion index within the section. Defaults to the end." }
|
|
254
|
+
},
|
|
255
|
+
["sectionId", "piece"],
|
|
256
|
+
(args) => {
|
|
257
|
+
const sectionId = str(args.sectionId);
|
|
258
|
+
const section = adapter.getValue().sections.find((s) => s.id === sectionId);
|
|
259
|
+
if (!section) return errorResult(`No section with id ${sectionId}`);
|
|
260
|
+
const spec = args.piece ?? {};
|
|
261
|
+
const piece = {
|
|
262
|
+
id: newId("p"),
|
|
263
|
+
...spec.label !== void 0 ? { label: str(spec.label) } : {},
|
|
264
|
+
...spec.width !== void 0 ? { width: num(spec.width) } : {},
|
|
265
|
+
...spec.height !== void 0 ? { height: num(spec.height) } : {},
|
|
266
|
+
content: coerceContent(spec.content),
|
|
267
|
+
pending: true
|
|
268
|
+
};
|
|
269
|
+
const insertAt = args.index !== void 0 ? Math.max(0, Math.min(num(args.index), section.pieces.length)) : section.pieces.length;
|
|
270
|
+
adapter.setValue((prev) => ({
|
|
271
|
+
sections: prev.sections.map(
|
|
272
|
+
(s) => s.id !== sectionId ? s : { ...s, pieces: [...s.pieces.slice(0, insertAt), piece, ...s.pieces.slice(insertAt)] }
|
|
273
|
+
)
|
|
274
|
+
}));
|
|
275
|
+
pushUndoEntry(agent.id, {
|
|
276
|
+
timestamp: Date.now(),
|
|
277
|
+
bridgeId: "artboard",
|
|
278
|
+
action: "artboard_add_piece",
|
|
279
|
+
label: `Added piece ${piece.id} to ${sectionId}`,
|
|
280
|
+
undo: () => adapter.setValue((prev) => ({
|
|
281
|
+
sections: prev.sections.map(
|
|
282
|
+
(s) => s.id !== sectionId ? s : { ...s, pieces: s.pieces.filter((p) => p.id !== piece.id) }
|
|
283
|
+
)
|
|
284
|
+
})),
|
|
285
|
+
redo: () => adapter.setValue((prev) => ({
|
|
286
|
+
sections: prev.sections.map(
|
|
287
|
+
(s) => s.id !== sectionId ? s : { ...s, pieces: [...s.pieces.slice(0, insertAt), piece, ...s.pieces.slice(insertAt)] }
|
|
288
|
+
)
|
|
289
|
+
}))
|
|
290
|
+
});
|
|
291
|
+
return textResult(`Added piece ${piece.id} to section ${sectionId} (pending)`, piece);
|
|
292
|
+
},
|
|
293
|
+
abTarget
|
|
294
|
+
);
|
|
295
|
+
reg(
|
|
296
|
+
"artboard_remove_piece",
|
|
297
|
+
"Remove a piece by id.",
|
|
298
|
+
{ pieceId: { type: "string" } },
|
|
299
|
+
["pieceId"],
|
|
300
|
+
(args) => {
|
|
301
|
+
const pieceId = str(args.pieceId);
|
|
302
|
+
let removed = null;
|
|
303
|
+
let fromSection = null;
|
|
304
|
+
let oldIndex = -1;
|
|
305
|
+
for (const s of adapter.getValue().sections) {
|
|
306
|
+
const idx = s.pieces.findIndex((p) => p.id === pieceId);
|
|
307
|
+
if (idx !== -1) {
|
|
308
|
+
removed = clone(s.pieces[idx]);
|
|
309
|
+
fromSection = s.id;
|
|
310
|
+
oldIndex = idx;
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (!removed || fromSection === null) return errorResult(`No piece with id ${pieceId}`);
|
|
315
|
+
const sectionId = fromSection;
|
|
316
|
+
const snapshot = removed;
|
|
317
|
+
const at = oldIndex;
|
|
318
|
+
adapter.setValue((prev) => ({
|
|
319
|
+
sections: prev.sections.map(
|
|
320
|
+
(s) => s.id !== sectionId ? s : { ...s, pieces: s.pieces.filter((p) => p.id !== pieceId) }
|
|
321
|
+
)
|
|
322
|
+
}));
|
|
323
|
+
pushUndoEntry(agent.id, {
|
|
324
|
+
timestamp: Date.now(),
|
|
325
|
+
bridgeId: "artboard",
|
|
326
|
+
action: "artboard_remove_piece",
|
|
327
|
+
label: `Removed piece ${pieceId}`,
|
|
328
|
+
undo: () => adapter.setValue((prev) => ({
|
|
329
|
+
sections: prev.sections.map(
|
|
330
|
+
(s) => s.id !== sectionId ? s : { ...s, pieces: [...s.pieces.slice(0, at), clone(snapshot), ...s.pieces.slice(at)] }
|
|
331
|
+
)
|
|
332
|
+
})),
|
|
333
|
+
redo: () => adapter.setValue((prev) => ({
|
|
334
|
+
sections: prev.sections.map(
|
|
335
|
+
(s) => s.id !== sectionId ? s : { ...s, pieces: s.pieces.filter((p) => p.id !== pieceId) }
|
|
336
|
+
)
|
|
337
|
+
}))
|
|
338
|
+
});
|
|
339
|
+
return textResult(`Removed piece ${pieceId}`, { id: pieceId });
|
|
340
|
+
},
|
|
341
|
+
abTarget
|
|
342
|
+
);
|
|
343
|
+
reg(
|
|
344
|
+
"artboard_reorder_piece",
|
|
345
|
+
"Move a piece to a new index, optionally into a different section.",
|
|
346
|
+
{
|
|
347
|
+
pieceId: { type: "string" },
|
|
348
|
+
sectionId: { type: "string", description: "Target section id. Defaults to the piece's current section." },
|
|
349
|
+
toIndex: { type: "number" }
|
|
350
|
+
},
|
|
351
|
+
["pieceId", "toIndex"],
|
|
352
|
+
(args) => {
|
|
353
|
+
const pieceId = str(args.pieceId);
|
|
354
|
+
const before = adapter.getValue();
|
|
355
|
+
let piece = null;
|
|
356
|
+
let srcSection = null;
|
|
357
|
+
for (const s of before.sections) {
|
|
358
|
+
const found = s.pieces.find((p) => p.id === pieceId);
|
|
359
|
+
if (found) {
|
|
360
|
+
piece = found;
|
|
361
|
+
srcSection = s.id;
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (!piece || srcSection === null) return errorResult(`No piece with id ${pieceId}`);
|
|
366
|
+
const dstSection = args.sectionId !== void 0 ? str(args.sectionId) : srcSection;
|
|
367
|
+
if (!before.sections.some((s) => s.id === dstSection)) return errorResult(`No section with id ${dstSection}`);
|
|
368
|
+
const toIndex = Math.max(0, num(args.toIndex));
|
|
369
|
+
const moving = piece;
|
|
370
|
+
const snapshot = clone(before);
|
|
371
|
+
adapter.setValue((prev) => {
|
|
372
|
+
const stripped = prev.sections.map((s) => ({ ...s, pieces: s.pieces.filter((p) => p.id !== pieceId) }));
|
|
373
|
+
return {
|
|
374
|
+
sections: stripped.map((s) => {
|
|
375
|
+
if (s.id !== dstSection) return s;
|
|
376
|
+
const clamped = Math.min(toIndex, s.pieces.length);
|
|
377
|
+
return { ...s, pieces: [...s.pieces.slice(0, clamped), moving, ...s.pieces.slice(clamped)] };
|
|
378
|
+
})
|
|
379
|
+
};
|
|
380
|
+
});
|
|
381
|
+
pushUndoEntry(agent.id, {
|
|
382
|
+
timestamp: Date.now(),
|
|
383
|
+
bridgeId: "artboard",
|
|
384
|
+
action: "artboard_reorder_piece",
|
|
385
|
+
label: `Reordered piece ${pieceId}`,
|
|
386
|
+
undo: () => adapter.setValue(() => clone(snapshot)),
|
|
387
|
+
redo: () => adapter.setValue((prev) => {
|
|
388
|
+
const stripped = prev.sections.map((s) => ({ ...s, pieces: s.pieces.filter((p) => p.id !== pieceId) }));
|
|
389
|
+
return {
|
|
390
|
+
sections: stripped.map((s) => {
|
|
391
|
+
if (s.id !== dstSection) return s;
|
|
392
|
+
const clamped = Math.min(toIndex, s.pieces.length);
|
|
393
|
+
return { ...s, pieces: [...s.pieces.slice(0, clamped), moving, ...s.pieces.slice(clamped)] };
|
|
394
|
+
})
|
|
395
|
+
};
|
|
396
|
+
})
|
|
397
|
+
});
|
|
398
|
+
return textResult(`Reordered piece ${pieceId} \u2192 ${dstSection}[${toIndex}]`, { id: pieceId, sectionId: dstSection, toIndex });
|
|
399
|
+
},
|
|
400
|
+
abTarget
|
|
401
|
+
);
|
|
402
|
+
reg(
|
|
403
|
+
"artboard_rename_piece",
|
|
404
|
+
"Set a piece's label.",
|
|
405
|
+
{ pieceId: { type: "string" }, label: { type: "string" } },
|
|
406
|
+
["pieceId", "label"],
|
|
407
|
+
(args) => {
|
|
408
|
+
const pieceId = str(args.pieceId);
|
|
409
|
+
const label = str(args.label);
|
|
410
|
+
const existing = adapter.getValue().sections.flatMap((s) => s.pieces).find((p) => p.id === pieceId);
|
|
411
|
+
if (!existing) return errorResult(`No piece with id ${pieceId}`);
|
|
412
|
+
const prevLabel = existing.label;
|
|
413
|
+
const apply = (l) => adapter.setValue((prev) => ({
|
|
414
|
+
sections: prev.sections.map((s) => ({
|
|
415
|
+
...s,
|
|
416
|
+
pieces: s.pieces.map((p) => p.id !== pieceId ? p : { ...p, ...l !== void 0 ? { label: l } : {} })
|
|
417
|
+
}))
|
|
418
|
+
}));
|
|
419
|
+
apply(label);
|
|
420
|
+
pushUndoEntry(agent.id, {
|
|
421
|
+
timestamp: Date.now(),
|
|
422
|
+
bridgeId: "artboard",
|
|
423
|
+
action: "artboard_rename_piece",
|
|
424
|
+
label: `Renamed piece ${pieceId}`,
|
|
425
|
+
undo: () => apply(prevLabel),
|
|
426
|
+
redo: () => apply(label)
|
|
427
|
+
});
|
|
428
|
+
return textResult(`Renamed piece ${pieceId} \u2192 "${label}"`, { id: pieceId, label });
|
|
429
|
+
},
|
|
430
|
+
abTarget
|
|
431
|
+
);
|
|
432
|
+
reg(
|
|
433
|
+
"artboard_set_piece_content",
|
|
434
|
+
"Replace a piece's content. content is { kind: 'image', src, alt? } | { kind: 'html', html } | { kind: 'node' }.",
|
|
435
|
+
{ pieceId: { type: "string" }, content: { type: "object" } },
|
|
436
|
+
["pieceId", "content"],
|
|
437
|
+
(args) => {
|
|
438
|
+
const pieceId = str(args.pieceId);
|
|
439
|
+
const existing = adapter.getValue().sections.flatMap((s) => s.pieces).find((p) => p.id === pieceId);
|
|
440
|
+
if (!existing) return errorResult(`No piece with id ${pieceId}`);
|
|
441
|
+
const prevContent = clone(existing.content);
|
|
442
|
+
const nextContent = coerceContent(args.content);
|
|
443
|
+
const apply = (c) => adapter.setValue((prev) => ({
|
|
444
|
+
sections: prev.sections.map((s) => ({
|
|
445
|
+
...s,
|
|
446
|
+
pieces: s.pieces.map((p) => p.id !== pieceId ? p : { ...p, content: clone(c) })
|
|
447
|
+
}))
|
|
448
|
+
}));
|
|
449
|
+
apply(nextContent);
|
|
450
|
+
pushUndoEntry(agent.id, {
|
|
451
|
+
timestamp: Date.now(),
|
|
452
|
+
bridgeId: "artboard",
|
|
453
|
+
action: "artboard_set_piece_content",
|
|
454
|
+
label: `Set content of piece ${pieceId}`,
|
|
455
|
+
undo: () => apply(prevContent),
|
|
456
|
+
redo: () => apply(nextContent)
|
|
457
|
+
});
|
|
458
|
+
return textResult(`Set content of piece ${pieceId} \u2192 ${nextContent.kind}`, { id: pieceId, content: nextContent });
|
|
459
|
+
},
|
|
460
|
+
abTarget
|
|
461
|
+
);
|
|
462
|
+
reg(
|
|
463
|
+
"artboard_focus_piece",
|
|
464
|
+
"Focus a piece by id (or pass null to clear focus).",
|
|
465
|
+
{ pieceId: { type: ["string", "null"] } },
|
|
466
|
+
[],
|
|
467
|
+
(args) => {
|
|
468
|
+
const raw = args.pieceId;
|
|
469
|
+
const pieceId = typeof raw === "string" ? raw : null;
|
|
470
|
+
if (pieceId !== null && !adapter.getValue().sections.some((s) => s.pieces.some((p) => p.id === pieceId))) {
|
|
471
|
+
return errorResult(`No piece with id ${pieceId}`);
|
|
472
|
+
}
|
|
473
|
+
adapter.setFocus(pieceId);
|
|
474
|
+
return textResult(pieceId ? `Focused piece ${pieceId}` : "Cleared focus", { id: pieceId });
|
|
475
|
+
},
|
|
476
|
+
abTarget
|
|
477
|
+
);
|
|
478
|
+
reg(
|
|
479
|
+
"artboard_add_section",
|
|
480
|
+
"Add a section. Provide an explicit id or one is generated.",
|
|
481
|
+
{
|
|
482
|
+
section: { type: "object", description: "Section spec: { id?, title, subtitle? }." },
|
|
483
|
+
index: { type: "number", description: "Insertion index. Defaults to the end." }
|
|
484
|
+
},
|
|
485
|
+
["section"],
|
|
486
|
+
(args) => {
|
|
487
|
+
const spec = args.section ?? {};
|
|
488
|
+
const section = {
|
|
489
|
+
id: spec.id !== void 0 ? str(spec.id) : newId("sec"),
|
|
490
|
+
title: str(spec.title),
|
|
491
|
+
...spec.subtitle !== void 0 ? { subtitle: str(spec.subtitle) } : {},
|
|
492
|
+
pieces: []
|
|
493
|
+
};
|
|
494
|
+
if (adapter.getValue().sections.some((s) => s.id === section.id)) {
|
|
495
|
+
return errorResult(`Section id ${section.id} already exists`);
|
|
496
|
+
}
|
|
497
|
+
const insertAt = args.index !== void 0 ? Math.max(0, Math.min(num(args.index), adapter.getValue().sections.length)) : adapter.getValue().sections.length;
|
|
498
|
+
adapter.setValue((prev) => ({
|
|
499
|
+
sections: [...prev.sections.slice(0, insertAt), section, ...prev.sections.slice(insertAt)]
|
|
500
|
+
}));
|
|
501
|
+
pushUndoEntry(agent.id, {
|
|
502
|
+
timestamp: Date.now(),
|
|
503
|
+
bridgeId: "artboard",
|
|
504
|
+
action: "artboard_add_section",
|
|
505
|
+
label: `Added section ${section.id}`,
|
|
506
|
+
undo: () => adapter.setValue((prev) => ({ sections: prev.sections.filter((s) => s.id !== section.id) })),
|
|
507
|
+
redo: () => adapter.setValue((prev) => ({
|
|
508
|
+
sections: [...prev.sections.slice(0, insertAt), section, ...prev.sections.slice(insertAt)]
|
|
509
|
+
}))
|
|
510
|
+
});
|
|
511
|
+
return textResult(`Added section ${section.id}`, section);
|
|
512
|
+
},
|
|
513
|
+
abTarget
|
|
514
|
+
);
|
|
515
|
+
reg(
|
|
516
|
+
"artboard_rename_section",
|
|
517
|
+
"Set a section's title and optionally its subtitle.",
|
|
518
|
+
{ sectionId: { type: "string" }, title: { type: "string" }, subtitle: { type: "string" } },
|
|
519
|
+
["sectionId", "title"],
|
|
520
|
+
(args) => {
|
|
521
|
+
const sectionId = str(args.sectionId);
|
|
522
|
+
const existing = adapter.getValue().sections.find((s) => s.id === sectionId);
|
|
523
|
+
if (!existing) return errorResult(`No section with id ${sectionId}`);
|
|
524
|
+
const prevTitle = existing.title;
|
|
525
|
+
const prevSubtitle = existing.subtitle;
|
|
526
|
+
const title = str(args.title);
|
|
527
|
+
const hasSubtitle = args.subtitle !== void 0;
|
|
528
|
+
const subtitle = hasSubtitle ? str(args.subtitle) : prevSubtitle;
|
|
529
|
+
const apply = (t, sub) => adapter.setValue((prev) => ({
|
|
530
|
+
sections: prev.sections.map(
|
|
531
|
+
(s) => s.id !== sectionId ? s : { ...s, title: t, ...sub !== void 0 ? { subtitle: sub } : {} }
|
|
532
|
+
)
|
|
533
|
+
}));
|
|
534
|
+
apply(title, subtitle);
|
|
535
|
+
pushUndoEntry(agent.id, {
|
|
536
|
+
timestamp: Date.now(),
|
|
537
|
+
bridgeId: "artboard",
|
|
538
|
+
action: "artboard_rename_section",
|
|
539
|
+
label: `Renamed section ${sectionId}`,
|
|
540
|
+
undo: () => apply(prevTitle, prevSubtitle),
|
|
541
|
+
redo: () => apply(title, subtitle)
|
|
542
|
+
});
|
|
543
|
+
return textResult(`Renamed section ${sectionId} \u2192 "${title}"`, { id: sectionId, title, subtitle });
|
|
544
|
+
},
|
|
545
|
+
abTarget
|
|
546
|
+
);
|
|
547
|
+
reg(
|
|
548
|
+
"artboard_set_viewport",
|
|
549
|
+
"Pan / zoom the viewport.",
|
|
550
|
+
{ x: { type: "number" }, y: { type: "number" }, zoom: { type: "number" } },
|
|
551
|
+
[],
|
|
552
|
+
(args) => {
|
|
553
|
+
const v = adapter.getViewport();
|
|
554
|
+
const next = {
|
|
555
|
+
x: args.x !== void 0 ? num(args.x) : v.x,
|
|
556
|
+
y: args.y !== void 0 ? num(args.y) : v.y,
|
|
557
|
+
zoom: args.zoom !== void 0 ? num(args.zoom) : v.zoom
|
|
558
|
+
};
|
|
559
|
+
adapter.setViewport(next);
|
|
560
|
+
return textResult(`Viewport \u2192 ${JSON.stringify(next)}`, next);
|
|
561
|
+
},
|
|
562
|
+
abTarget
|
|
563
|
+
);
|
|
564
|
+
return {
|
|
565
|
+
id: "artboard",
|
|
566
|
+
title: "Artboard",
|
|
567
|
+
dispose: () => {
|
|
568
|
+
for (const d of disposers) d();
|
|
569
|
+
adapter.setAgentCursor?.(null);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
exports.registerArtboardBridge = registerArtboardBridge;
|
|
575
|
+
//# sourceMappingURL=bridges-artboard.cjs.map
|
|
576
|
+
//# sourceMappingURL=bridges-artboard.cjs.map
|