pmx-canvas 0.3.0 → 0.3.2
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/CHANGELOG.md +73 -0
- package/dist/canvas/index.js +65 -65
- package/dist/types/cli/agent.d.ts +9 -1
- package/dist/types/cli/daemon.d.ts +74 -0
- package/dist/types/cli/watch.d.ts +2 -2
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
- package/dist/types/client/canvas/Minimap.d.ts +1 -1
- package/dist/types/client/nodes/ExtAppFrame.d.ts +9 -1
- package/dist/types/client/nodes/FileNode.d.ts +1 -1
- package/dist/types/client/nodes/ImageNode.d.ts +1 -1
- package/dist/types/client/nodes/InlineFormatBar.d.ts +1 -1
- package/dist/types/client/nodes/MarkdownNode.d.ts +1 -1
- package/dist/types/client/nodes/PromptNode.d.ts +1 -1
- package/dist/types/client/nodes/ResponseNode.d.ts +1 -1
- package/dist/types/server/canvas-schema.d.ts +1 -1
- package/dist/types/server/html-primitives.d.ts +1 -1
- package/dist/types/server/index.d.ts +4 -4
- package/dist/types/server/operations/index.d.ts +1 -1
- package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
- package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
- package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
- package/dist/types/server/server.d.ts +8 -0
- package/docs/cli.md +10 -1
- package/docs/http-api.md +28 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
- package/docs/screenshot.png +0 -0
- package/docs/tech-debt-assessment-2026-07.md +2 -2
- package/package.json +5 -2
- package/skills/pmx-canvas/SKILL.md +15 -10
- package/skills/pmx-canvas/references/full-reference.md +17 -3
- package/src/cli/agent.ts +1951 -1571
- package/src/cli/daemon.ts +460 -0
- package/src/cli/index.ts +80 -323
- package/src/cli/watch.ts +2 -10
- package/src/client/App.tsx +48 -46
- package/src/client/canvas/AttentionHistory.tsx +11 -1
- package/src/client/canvas/CanvasNode.tsx +41 -29
- package/src/client/canvas/CanvasViewport.tsx +101 -66
- package/src/client/canvas/CommandPalette.tsx +61 -27
- package/src/client/canvas/ContextMenu.tsx +13 -20
- package/src/client/canvas/ContextPinBar.tsx +1 -5
- package/src/client/canvas/ContextPinHud.tsx +1 -6
- package/src/client/canvas/DockedNode.tsx +4 -4
- package/src/client/canvas/EdgeLayer.tsx +37 -36
- package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
- package/src/client/canvas/FocusFieldLayer.tsx +20 -22
- package/src/client/canvas/IntentLayer.tsx +31 -14
- package/src/client/canvas/Minimap.tsx +11 -16
- package/src/client/canvas/SelectionBar.tsx +4 -11
- package/src/client/canvas/ShortcutOverlay.tsx +3 -1
- package/src/client/canvas/SnapshotPanel.tsx +77 -95
- package/src/client/canvas/auto-fit.ts +15 -14
- package/src/client/canvas/snap-guides.ts +12 -12
- package/src/client/canvas/use-node-resize.ts +1 -5
- package/src/client/canvas/use-pan-zoom.ts +25 -26
- package/src/client/ext-app/bridge.ts +3 -12
- package/src/client/icons.tsx +63 -20
- package/src/client/nodes/ContextNode.tsx +14 -25
- package/src/client/nodes/ExtAppFrame.tsx +194 -80
- package/src/client/nodes/FileNode.tsx +74 -62
- package/src/client/nodes/GroupNode.tsx +4 -6
- package/src/client/nodes/HtmlNode.tsx +76 -46
- package/src/client/nodes/ImageNode.tsx +18 -27
- package/src/client/nodes/InlineFormatBar.tsx +4 -21
- package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
- package/src/client/nodes/LedgerNode.tsx +10 -4
- package/src/client/nodes/MarkdownNode.tsx +3 -10
- package/src/client/nodes/McpAppNode.tsx +26 -22
- package/src/client/nodes/MdFormatBar.tsx +10 -7
- package/src/client/nodes/PromptNode.tsx +23 -51
- package/src/client/nodes/ResponseNode.tsx +3 -13
- package/src/client/nodes/StatusNode.tsx +5 -9
- package/src/client/nodes/StatusSummary.tsx +2 -8
- package/src/client/nodes/WebpageNode.tsx +20 -14
- package/src/client/nodes/iframe-document-url.ts +25 -16
- package/src/client/nodes/image-warnings.ts +1 -7
- package/src/client/nodes/md-format.ts +20 -5
- package/src/client/state/attention-bridge.ts +4 -9
- package/src/client/state/attention-store.ts +1 -7
- package/src/client/state/canvas-store.ts +52 -36
- package/src/client/state/intent-bridge.ts +176 -112
- package/src/client/state/intent-store.ts +4 -1
- package/src/client/state/sse-bridge.ts +53 -70
- package/src/json-render/catalog.ts +12 -16
- package/src/json-render/charts/components.tsx +16 -20
- package/src/json-render/charts/extra-components.tsx +8 -16
- package/src/json-render/charts/extra-definitions.ts +1 -2
- package/src/json-render/charts/tufte-components.tsx +37 -20
- package/src/json-render/charts/tufte-definitions.ts +8 -2
- package/src/json-render/renderer/index.tsx +42 -22
- package/src/json-render/schema.ts +6 -3
- package/src/json-render/server.ts +33 -39
- package/src/mcp/canvas-access.ts +35 -21
- package/src/mcp/server.ts +132 -70
- package/src/server/agent-context.ts +63 -36
- package/src/server/ax-context.ts +7 -5
- package/src/server/ax-interaction.ts +176 -43
- package/src/server/ax-state-manager.ts +182 -39
- package/src/server/ax-state.ts +142 -47
- package/src/server/canvas-db.ts +213 -95
- package/src/server/canvas-operations.ts +180 -123
- package/src/server/canvas-provenance.ts +1 -4
- package/src/server/canvas-schema.ts +454 -73
- package/src/server/canvas-serialization.ts +27 -35
- package/src/server/canvas-state.ts +150 -58
- package/src/server/chart-template.ts +4 -5
- package/src/server/code-graph.ts +19 -6
- package/src/server/diagram-presets.ts +28 -29
- package/src/server/ext-app-lookup.ts +3 -12
- package/src/server/html-node-summary.ts +19 -10
- package/src/server/html-primitives.ts +326 -97
- package/src/server/html-surface.ts +6 -9
- package/src/server/image-source.ts +6 -4
- package/src/server/index.ts +320 -217
- package/src/server/intent-registry.ts +2 -5
- package/src/server/mcp-app-candidate.ts +5 -10
- package/src/server/mcp-app-host.ts +14 -38
- package/src/server/mcp-app-runtime.ts +12 -20
- package/src/server/mutation-history.ts +15 -5
- package/src/server/operations/composites.ts +1 -3
- package/src/server/operations/http.ts +2 -3
- package/src/server/operations/index.ts +7 -1
- package/src/server/operations/invoker.ts +4 -3
- package/src/server/operations/mcp.ts +22 -30
- package/src/server/operations/ops/annotation.ts +122 -10
- package/src/server/operations/ops/app.ts +98 -73
- package/src/server/operations/ops/ax-await.ts +17 -10
- package/src/server/operations/ops/ax-read.ts +347 -0
- package/src/server/operations/ops/ax-shared.ts +2 -7
- package/src/server/operations/ops/ax-state.ts +32 -14
- package/src/server/operations/ops/ax-timeline.ts +32 -19
- package/src/server/operations/ops/ax-work.ts +54 -37
- package/src/server/operations/ops/batch.ts +41 -15
- package/src/server/operations/ops/canvas-wire.ts +91 -0
- package/src/server/operations/ops/edges.ts +37 -25
- package/src/server/operations/ops/ext-app.ts +346 -0
- package/src/server/operations/ops/groups.ts +49 -20
- package/src/server/operations/ops/intent.ts +18 -12
- package/src/server/operations/ops/json-render.ts +239 -98
- package/src/server/operations/ops/nodes.ts +298 -109
- package/src/server/operations/ops/query.ts +46 -28
- package/src/server/operations/ops/snapshots.ts +35 -26
- package/src/server/operations/ops/validate.ts +2 -1
- package/src/server/operations/ops/viewport.ts +60 -16
- package/src/server/operations/ops/webview.ts +44 -18
- package/src/server/operations/registry.ts +2 -3
- package/src/server/operations/types.ts +7 -5
- package/src/server/operations/webview-runner.ts +1 -3
- package/src/server/placement.ts +8 -18
- package/src/server/server.ts +122 -1028
- package/src/server/spatial-analysis.ts +39 -25
- package/src/server/trace-manager.ts +3 -8
- package/src/server/web-artifacts.ts +23 -27
- package/src/server/webpage-node.ts +5 -13
- package/src/shared/auto-arrange.ts +12 -5
- package/src/shared/content-height-reporter.ts +8 -6
- package/src/shared/ext-app-tool-result.ts +2 -6
- package/src/shared/placement.ts +1 -4
- package/src/shared/semantic-attention.ts +39 -37
- package/src/shared/surface.ts +8 -4
|
@@ -2,12 +2,7 @@ function logRequestError(action: string, error: unknown): void {
|
|
|
2
2
|
console.error(`[intent-bridge] ${action} failed`, error);
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
async function requestJson<T>(
|
|
6
|
-
action: string,
|
|
7
|
-
url: string,
|
|
8
|
-
fallback: T,
|
|
9
|
-
init?: RequestInit,
|
|
10
|
-
): Promise<T> {
|
|
5
|
+
async function requestJson<T>(action: string, url: string, fallback: T, init?: RequestInit): Promise<T> {
|
|
11
6
|
try {
|
|
12
7
|
const res = await fetch(url, init);
|
|
13
8
|
return (await res.json()) as T;
|
|
@@ -17,11 +12,7 @@ async function requestJson<T>(
|
|
|
17
12
|
}
|
|
18
13
|
}
|
|
19
14
|
|
|
20
|
-
async function requestOk(
|
|
21
|
-
action: string,
|
|
22
|
-
url: string,
|
|
23
|
-
init?: RequestInit,
|
|
24
|
-
): Promise<{ ok: boolean }> {
|
|
15
|
+
async function requestOk(action: string, url: string, init?: RequestInit): Promise<{ ok: boolean }> {
|
|
25
16
|
try {
|
|
26
17
|
const res = await fetch(url, init);
|
|
27
18
|
return { ok: res.ok };
|
|
@@ -31,11 +22,7 @@ async function requestOk(
|
|
|
31
22
|
}
|
|
32
23
|
}
|
|
33
24
|
|
|
34
|
-
async function requestBestEffort(
|
|
35
|
-
action: string,
|
|
36
|
-
url: string,
|
|
37
|
-
init?: RequestInit,
|
|
38
|
-
): Promise<void> {
|
|
25
|
+
async function requestBestEffort(action: string, url: string, init?: RequestInit): Promise<void> {
|
|
39
26
|
try {
|
|
40
27
|
await fetch(url, init);
|
|
41
28
|
} catch (error) {
|
|
@@ -44,15 +31,17 @@ async function requestBestEffort(
|
|
|
44
31
|
}
|
|
45
32
|
|
|
46
33
|
/** Dispatch user intents from the canvas to the server (for TUI consumption). */
|
|
47
|
-
export async function sendIntent(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
34
|
+
export async function sendIntent(type: string, payload: Record<string, unknown> = {}): Promise<{ ok: boolean }> {
|
|
35
|
+
return requestJson(
|
|
36
|
+
'sendIntent',
|
|
37
|
+
'/api/workbench/intent',
|
|
38
|
+
{ ok: false },
|
|
39
|
+
{
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
body: JSON.stringify({ type, payload }),
|
|
43
|
+
},
|
|
44
|
+
);
|
|
56
45
|
}
|
|
57
46
|
|
|
58
47
|
/**
|
|
@@ -88,11 +77,16 @@ export async function vetoGhostIntent(intent: {
|
|
|
88
77
|
|
|
89
78
|
/** Fetch rendered markdown HTML from the server. */
|
|
90
79
|
export async function renderMarkdown(markdown: string): Promise<string> {
|
|
91
|
-
const data = await requestJson<{ html?: string }>(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
80
|
+
const data = await requestJson<{ html?: string }>(
|
|
81
|
+
'renderMarkdown',
|
|
82
|
+
'/api/render',
|
|
83
|
+
{},
|
|
84
|
+
{
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: { 'Content-Type': 'application/json' },
|
|
87
|
+
body: JSON.stringify({ markdown }),
|
|
88
|
+
},
|
|
89
|
+
);
|
|
96
90
|
return data.html ?? '';
|
|
97
91
|
}
|
|
98
92
|
|
|
@@ -105,15 +99,17 @@ export async function fetchFile(path: string): Promise<{
|
|
|
105
99
|
}
|
|
106
100
|
|
|
107
101
|
/** Save file content to the server. */
|
|
108
|
-
export async function saveFile(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
102
|
+
export async function saveFile(path: string, content: string): Promise<{ ok: boolean; updatedAt?: string }> {
|
|
103
|
+
return requestJson(
|
|
104
|
+
'saveFile',
|
|
105
|
+
'/api/file/save',
|
|
106
|
+
{ ok: false },
|
|
107
|
+
{
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: { 'Content-Type': 'application/json' },
|
|
110
|
+
body: JSON.stringify({ path, content }),
|
|
111
|
+
},
|
|
112
|
+
);
|
|
117
113
|
}
|
|
118
114
|
|
|
119
115
|
/** Fetch current workbench state. */
|
|
@@ -136,11 +132,16 @@ export async function fetchCanvasState(): Promise<Record<string, unknown>> {
|
|
|
136
132
|
}
|
|
137
133
|
|
|
138
134
|
export async function saveCanvasTheme(theme: string): Promise<{ ok: boolean; theme?: string }> {
|
|
139
|
-
return requestJson(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
135
|
+
return requestJson(
|
|
136
|
+
'saveCanvasTheme',
|
|
137
|
+
'/api/canvas/theme',
|
|
138
|
+
{ ok: false },
|
|
139
|
+
{
|
|
140
|
+
method: 'POST',
|
|
141
|
+
headers: { 'Content-Type': 'application/json' },
|
|
142
|
+
body: JSON.stringify({ theme }),
|
|
143
|
+
},
|
|
144
|
+
);
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
/** Fetch available slash commands for prompt completion. */
|
|
@@ -157,17 +158,22 @@ export async function submitCanvasPrompt(
|
|
|
157
158
|
threadNodeId?: string,
|
|
158
159
|
): Promise<{ ok: boolean; nodeId?: string; error?: string }> {
|
|
159
160
|
if (!text.trim()) return { ok: false, error: 'Prompt text is required' };
|
|
160
|
-
return requestJson(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
161
|
+
return requestJson(
|
|
162
|
+
'submitCanvasPrompt',
|
|
163
|
+
'/api/canvas/prompt',
|
|
164
|
+
{ ok: false, error: 'Network error' },
|
|
165
|
+
{
|
|
166
|
+
method: 'POST',
|
|
167
|
+
headers: { 'Content-Type': 'application/json' },
|
|
168
|
+
body: JSON.stringify({
|
|
169
|
+
text,
|
|
170
|
+
...(position ? { position } : {}),
|
|
171
|
+
...(parentNodeId ? { parentNodeId } : {}),
|
|
172
|
+
...(contextNodeIds && contextNodeIds.length > 0 ? { contextNodeIds } : {}),
|
|
173
|
+
...(threadNodeId ? { threadNodeId } : {}),
|
|
174
|
+
}),
|
|
175
|
+
},
|
|
176
|
+
);
|
|
171
177
|
}
|
|
172
178
|
|
|
173
179
|
/** Submit a reply into an existing prompt thread. */
|
|
@@ -203,11 +209,16 @@ export async function createEdgeFromClient(
|
|
|
203
209
|
type: string,
|
|
204
210
|
label?: string,
|
|
205
211
|
): Promise<{ ok: boolean; id?: string }> {
|
|
206
|
-
return requestJson(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
212
|
+
return requestJson(
|
|
213
|
+
'createEdgeFromClient',
|
|
214
|
+
'/api/canvas/edge',
|
|
215
|
+
{ ok: false },
|
|
216
|
+
{
|
|
217
|
+
method: 'POST',
|
|
218
|
+
headers: { 'Content-Type': 'application/json' },
|
|
219
|
+
body: JSON.stringify({ from, to, type, label }),
|
|
220
|
+
},
|
|
221
|
+
);
|
|
211
222
|
}
|
|
212
223
|
|
|
213
224
|
/** Create a canvas node via the server. Returns the new node ID. */
|
|
@@ -220,11 +231,16 @@ export async function createNodeFromClient(opts: {
|
|
|
220
231
|
width?: number;
|
|
221
232
|
height?: number;
|
|
222
233
|
}): Promise<{ ok: boolean; id?: string }> {
|
|
223
|
-
return requestJson(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
234
|
+
return requestJson(
|
|
235
|
+
'createNodeFromClient',
|
|
236
|
+
'/api/canvas/node',
|
|
237
|
+
{ ok: false },
|
|
238
|
+
{
|
|
239
|
+
method: 'POST',
|
|
240
|
+
headers: { 'Content-Type': 'application/json' },
|
|
241
|
+
body: JSON.stringify(opts),
|
|
242
|
+
},
|
|
243
|
+
);
|
|
228
244
|
}
|
|
229
245
|
|
|
230
246
|
/** Update a canvas node via the server. */
|
|
@@ -241,11 +257,16 @@ export async function updateNodeFromClient(
|
|
|
241
257
|
data?: Record<string, unknown>;
|
|
242
258
|
},
|
|
243
259
|
): Promise<{ ok: boolean; id?: string }> {
|
|
244
|
-
return requestJson(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
260
|
+
return requestJson(
|
|
261
|
+
'updateNodeFromClient',
|
|
262
|
+
`/api/canvas/node/${encodeURIComponent(id)}`,
|
|
263
|
+
{ ok: false },
|
|
264
|
+
{
|
|
265
|
+
method: 'PATCH',
|
|
266
|
+
headers: { 'Content-Type': 'application/json' },
|
|
267
|
+
body: JSON.stringify(patch),
|
|
268
|
+
},
|
|
269
|
+
);
|
|
249
270
|
}
|
|
250
271
|
|
|
251
272
|
/** Refresh a webpage node from its persisted URL on the server. */
|
|
@@ -253,18 +274,28 @@ export async function refreshWebpageNodeFromClient(
|
|
|
253
274
|
id: string,
|
|
254
275
|
url?: string,
|
|
255
276
|
): Promise<{ ok: boolean; id?: string; error?: string }> {
|
|
256
|
-
return requestJson(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
277
|
+
return requestJson(
|
|
278
|
+
'refreshWebpageNodeFromClient',
|
|
279
|
+
`/api/canvas/node/${encodeURIComponent(id)}/refresh`,
|
|
280
|
+
{ ok: false },
|
|
281
|
+
{
|
|
282
|
+
method: 'POST',
|
|
283
|
+
headers: { 'Content-Type': 'application/json' },
|
|
284
|
+
body: JSON.stringify(url ? { url } : {}),
|
|
285
|
+
},
|
|
286
|
+
);
|
|
261
287
|
}
|
|
262
288
|
|
|
263
289
|
/** Remove a canvas node via the server. */
|
|
264
290
|
export async function removeNodeFromClient(id: string): Promise<{ ok: boolean; removed?: string }> {
|
|
265
|
-
return requestJson(
|
|
266
|
-
|
|
267
|
-
|
|
291
|
+
return requestJson(
|
|
292
|
+
'removeNodeFromClient',
|
|
293
|
+
`/api/canvas/node/${encodeURIComponent(id)}`,
|
|
294
|
+
{ ok: false },
|
|
295
|
+
{
|
|
296
|
+
method: 'DELETE',
|
|
297
|
+
},
|
|
298
|
+
);
|
|
268
299
|
}
|
|
269
300
|
|
|
270
301
|
// ── PMX-AX node interactions ──────────────────────────────────
|
|
@@ -292,7 +323,10 @@ export async function fetchAxSurfaceState(): Promise<unknown> {
|
|
|
292
323
|
}
|
|
293
324
|
|
|
294
325
|
/** Ask the server to open a node's surface in the system browser. */
|
|
295
|
-
export async function openNodeInSystemBrowserRequest(
|
|
326
|
+
export async function openNodeInSystemBrowserRequest(
|
|
327
|
+
nodeId: string,
|
|
328
|
+
url?: string,
|
|
329
|
+
): Promise<{ ok: boolean; opened: boolean }> {
|
|
296
330
|
return requestJson<{ ok: boolean; opened: boolean }>(
|
|
297
331
|
'openNodeInSystemBrowserRequest',
|
|
298
332
|
'/api/canvas/open-external',
|
|
@@ -324,14 +358,19 @@ export async function updateViewportFromClient(
|
|
|
324
358
|
viewport: { x: number; y: number; scale: number },
|
|
325
359
|
options: { recordHistory?: boolean } = {},
|
|
326
360
|
): Promise<{ ok: boolean }> {
|
|
327
|
-
return requestJson(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
361
|
+
return requestJson(
|
|
362
|
+
'updateViewportFromClient',
|
|
363
|
+
'/api/canvas/viewport',
|
|
364
|
+
{ ok: false },
|
|
365
|
+
{
|
|
366
|
+
method: 'POST',
|
|
367
|
+
headers: { 'Content-Type': 'application/json' },
|
|
368
|
+
body: JSON.stringify({
|
|
369
|
+
...viewport,
|
|
370
|
+
...(options.recordHistory === false ? { recordHistory: false } : {}),
|
|
371
|
+
}),
|
|
372
|
+
},
|
|
373
|
+
);
|
|
335
374
|
}
|
|
336
375
|
|
|
337
376
|
// ── Group API ─────────────────────────────────────────────────
|
|
@@ -346,29 +385,44 @@ export async function createGroupFromClient(opts: {
|
|
|
346
385
|
width?: number;
|
|
347
386
|
height?: number;
|
|
348
387
|
}): Promise<{ ok: boolean; id?: string }> {
|
|
349
|
-
return requestJson(
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
388
|
+
return requestJson(
|
|
389
|
+
'createGroupFromClient',
|
|
390
|
+
'/api/canvas/group',
|
|
391
|
+
{ ok: false },
|
|
392
|
+
{
|
|
393
|
+
method: 'POST',
|
|
394
|
+
headers: { 'Content-Type': 'application/json' },
|
|
395
|
+
body: JSON.stringify(opts),
|
|
396
|
+
},
|
|
397
|
+
);
|
|
354
398
|
}
|
|
355
399
|
|
|
356
400
|
/** Add nodes to an existing group. */
|
|
357
401
|
export async function addToGroupFromClient(groupId: string, childIds: string[]): Promise<{ ok: boolean }> {
|
|
358
|
-
return requestJson(
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
402
|
+
return requestJson(
|
|
403
|
+
'addToGroupFromClient',
|
|
404
|
+
'/api/canvas/group/add',
|
|
405
|
+
{ ok: false },
|
|
406
|
+
{
|
|
407
|
+
method: 'POST',
|
|
408
|
+
headers: { 'Content-Type': 'application/json' },
|
|
409
|
+
body: JSON.stringify({ groupId, childIds }),
|
|
410
|
+
},
|
|
411
|
+
);
|
|
363
412
|
}
|
|
364
413
|
|
|
365
414
|
/** Ungroup all children from a group. */
|
|
366
415
|
export async function ungroupFromClient(groupId: string): Promise<{ ok: boolean }> {
|
|
367
|
-
return requestJson(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
416
|
+
return requestJson(
|
|
417
|
+
'ungroupFromClient',
|
|
418
|
+
'/api/canvas/group/ungroup',
|
|
419
|
+
{ ok: false },
|
|
420
|
+
{
|
|
421
|
+
method: 'POST',
|
|
422
|
+
headers: { 'Content-Type': 'application/json' },
|
|
423
|
+
body: JSON.stringify({ groupId }),
|
|
424
|
+
},
|
|
425
|
+
);
|
|
372
426
|
}
|
|
373
427
|
|
|
374
428
|
// ── Snapshot API ──────────────────────────────────────────────
|
|
@@ -386,11 +440,16 @@ export async function listSnapshots(): Promise<CanvasSnapshotInfo[]> {
|
|
|
386
440
|
}
|
|
387
441
|
|
|
388
442
|
export async function saveSnapshot(name: string): Promise<{ ok: boolean; snapshot?: CanvasSnapshotInfo }> {
|
|
389
|
-
return requestJson(
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
443
|
+
return requestJson(
|
|
444
|
+
'saveSnapshot',
|
|
445
|
+
'/api/canvas/snapshots',
|
|
446
|
+
{ ok: false },
|
|
447
|
+
{
|
|
448
|
+
method: 'POST',
|
|
449
|
+
headers: { 'Content-Type': 'application/json' },
|
|
450
|
+
body: JSON.stringify({ name }),
|
|
451
|
+
},
|
|
452
|
+
);
|
|
394
453
|
}
|
|
395
454
|
|
|
396
455
|
export async function restoreSnapshot(id: string): Promise<{ ok: boolean }> {
|
|
@@ -403,9 +462,14 @@ export async function deleteSnapshot(id: string): Promise<{ ok: boolean }> {
|
|
|
403
462
|
|
|
404
463
|
/** Remove a canvas edge via the server. */
|
|
405
464
|
export async function removeEdgeFromClient(edgeId: string): Promise<{ ok: boolean }> {
|
|
406
|
-
return requestJson(
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
465
|
+
return requestJson(
|
|
466
|
+
'removeEdgeFromClient',
|
|
467
|
+
'/api/canvas/edge',
|
|
468
|
+
{ ok: false },
|
|
469
|
+
{
|
|
470
|
+
method: 'DELETE',
|
|
471
|
+
headers: { 'Content-Type': 'application/json' },
|
|
472
|
+
body: JSON.stringify({ edge_id: edgeId }),
|
|
473
|
+
},
|
|
474
|
+
);
|
|
411
475
|
}
|
|
@@ -63,7 +63,10 @@ function setPhase(id: string, phase: IntentPhase, ms: number, settledNodeId?: st
|
|
|
63
63
|
next.set(id, { ...current, phase, ...(settledNodeId ? { settledNodeId } : {}) });
|
|
64
64
|
writeIntents(next);
|
|
65
65
|
clearExitTimer(id);
|
|
66
|
-
exitTimers.set(
|
|
66
|
+
exitTimers.set(
|
|
67
|
+
id,
|
|
68
|
+
setTimeout(() => removeIntent(id), ms),
|
|
69
|
+
);
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
/** Resolve a ghost into a real node — the settle morph, then removal. */
|
|
@@ -72,10 +72,8 @@ function applyLayoutOverrides(node: CanvasNodeState): CanvasNodeState {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// ── Default positions by type ─────────────────────────────────
|
|
75
|
-
const DEFAULT_POSITIONS: Record<
|
|
76
|
-
|
|
77
|
-
{ x: number; y: number; w: number; h: number }
|
|
78
|
-
> & Record<'prompt' | 'response', { x: number; y: number; w: number; h: number }> = {
|
|
75
|
+
const DEFAULT_POSITIONS: Record<CanvasNodeState['type'], { x: number; y: number; w: number; h: number }> &
|
|
76
|
+
Record<'prompt' | 'response', { x: number; y: number; w: number; h: number }> = {
|
|
79
77
|
status: { x: 40, y: 80, w: 300, h: 120 },
|
|
80
78
|
markdown: { x: 380, y: 80, w: 720, h: 600 },
|
|
81
79
|
context: { x: 1130, y: 80, w: 320, h: 400 },
|
|
@@ -114,11 +112,7 @@ function makeNode(
|
|
|
114
112
|
}
|
|
115
113
|
|
|
116
114
|
function getMarkdownPlacement(): { x: number; y: number } {
|
|
117
|
-
return findOpenCanvasPosition(
|
|
118
|
-
[...nodes.value.values()],
|
|
119
|
-
DEFAULT_POSITIONS.markdown.w,
|
|
120
|
-
DEFAULT_POSITIONS.markdown.h,
|
|
121
|
-
);
|
|
115
|
+
return findOpenCanvasPosition([...nodes.value.values()], DEFAULT_POSITIONS.markdown.w, DEFAULT_POSITIONS.markdown.h);
|
|
122
116
|
}
|
|
123
117
|
|
|
124
118
|
// ── Node ensure helpers ───────────────────────────────────────
|
|
@@ -243,11 +237,7 @@ function findExtAppNodeId(toolCallId: string): string | null {
|
|
|
243
237
|
const legacyDirectId = `ext-app-${toolCallId}`;
|
|
244
238
|
if (legacyDirectId !== directId && nodes.value.has(legacyDirectId)) return legacyDirectId;
|
|
245
239
|
for (const [nodeId, node] of nodes.value.entries()) {
|
|
246
|
-
if (
|
|
247
|
-
node.type === 'mcp-app' &&
|
|
248
|
-
node.data.mode === 'ext-app' &&
|
|
249
|
-
node.data.toolCallId === toolCallId
|
|
250
|
-
) {
|
|
240
|
+
if (node.type === 'mcp-app' && node.data.mode === 'ext-app' && node.data.toolCallId === toolCallId) {
|
|
251
241
|
return nodeId;
|
|
252
242
|
}
|
|
253
243
|
}
|
|
@@ -301,28 +291,27 @@ function applyCanvasTheme(theme: string): void {
|
|
|
301
291
|
}
|
|
302
292
|
|
|
303
293
|
function isCanvasNodeType(value: unknown): value is CanvasNodeState['type'] {
|
|
304
|
-
return
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
294
|
+
return (
|
|
295
|
+
value === 'markdown' ||
|
|
296
|
+
value === 'mcp-app' ||
|
|
297
|
+
value === 'webpage' ||
|
|
298
|
+
value === 'json-render' ||
|
|
299
|
+
value === 'graph' ||
|
|
300
|
+
value === 'prompt' ||
|
|
301
|
+
value === 'response' ||
|
|
302
|
+
value === 'status' ||
|
|
303
|
+
value === 'context' ||
|
|
304
|
+
value === 'ledger' ||
|
|
305
|
+
value === 'trace' ||
|
|
306
|
+
value === 'file' ||
|
|
307
|
+
value === 'image' ||
|
|
308
|
+
value === 'html' ||
|
|
309
|
+
value === 'group'
|
|
310
|
+
);
|
|
319
311
|
}
|
|
320
312
|
|
|
321
313
|
function isCanvasEdgeType(value: unknown): value is CanvasEdge['type'] {
|
|
322
|
-
return value === 'relation'
|
|
323
|
-
|| value === 'depends-on'
|
|
324
|
-
|| value === 'flow'
|
|
325
|
-
|| value === 'references';
|
|
314
|
+
return value === 'relation' || value === 'depends-on' || value === 'flow' || value === 'references';
|
|
326
315
|
}
|
|
327
316
|
|
|
328
317
|
function parseCanvasPosition(value: unknown): { x: number; y: number } | null {
|
|
@@ -353,10 +342,8 @@ function parseCanvasNode(raw: Record<string, unknown>): CanvasNodeState | null {
|
|
|
353
342
|
const size = parseCanvasSize(raw.size);
|
|
354
343
|
if (!position || !size) return null;
|
|
355
344
|
|
|
356
|
-
const dockPosition =
|
|
357
|
-
|
|
358
|
-
const data =
|
|
359
|
-
raw.data && typeof raw.data === 'object' ? Object.fromEntries(Object.entries(raw.data)) : {};
|
|
345
|
+
const dockPosition = raw.dockPosition === 'left' || raw.dockPosition === 'right' ? raw.dockPosition : null;
|
|
346
|
+
const data = raw.data && typeof raw.data === 'object' ? Object.fromEntries(Object.entries(raw.data)) : {};
|
|
360
347
|
|
|
361
348
|
return {
|
|
362
349
|
id: raw.id,
|
|
@@ -383,9 +370,7 @@ function parseCanvasEdge(raw: Record<string, unknown>): CanvasEdge | null {
|
|
|
383
370
|
to: raw.to,
|
|
384
371
|
type: raw.type,
|
|
385
372
|
...(typeof raw.label === 'string' ? { label: raw.label } : {}),
|
|
386
|
-
...(raw.style === 'solid' || raw.style === 'dashed' || raw.style === 'dotted'
|
|
387
|
-
? { style: raw.style }
|
|
388
|
-
: {}),
|
|
373
|
+
...(raw.style === 'solid' || raw.style === 'dashed' || raw.style === 'dotted' ? { style: raw.style } : {}),
|
|
389
374
|
...(raw.animated === true ? { animated: true } : {}),
|
|
390
375
|
};
|
|
391
376
|
}
|
|
@@ -429,8 +414,7 @@ function handleWorkbenchOpen(data: Record<string, unknown>): void {
|
|
|
429
414
|
// H6: Guard — path must be a string for node ID stability
|
|
430
415
|
if (typeof data.path !== 'string' || !data.path) return;
|
|
431
416
|
const path = data.path;
|
|
432
|
-
const title =
|
|
433
|
-
(typeof data.title === 'string' ? data.title : '') || path.split('/').pop() || 'Untitled';
|
|
417
|
+
const title = (typeof data.title === 'string' ? data.title : '') || path.split('/').pop() || 'Untitled';
|
|
434
418
|
|
|
435
419
|
ensureMarkdownNode(path, title);
|
|
436
420
|
if (data.ledgerSummary) {
|
|
@@ -515,9 +499,7 @@ function handleAuxClose(data: Record<string, unknown>): void {
|
|
|
515
499
|
} else {
|
|
516
500
|
const existing = nodes.value.get(id);
|
|
517
501
|
if (!existing) return;
|
|
518
|
-
const auxTabs = ((existing.data.auxTabs as Array<Record<string, unknown>>) ?? []).filter(
|
|
519
|
-
(t) => t.id !== data.id,
|
|
520
|
-
);
|
|
502
|
+
const auxTabs = ((existing.data.auxTabs as Array<Record<string, unknown>>) ?? []).filter((t) => t.id !== data.id);
|
|
521
503
|
updateNodeData(id, { auxTabs });
|
|
522
504
|
}
|
|
523
505
|
}
|
|
@@ -587,8 +569,7 @@ function handleExtAppOpen(data: Record<string, unknown>): void {
|
|
|
587
569
|
|
|
588
570
|
function handleExtAppUpdate(data: Record<string, unknown>): void {
|
|
589
571
|
if (typeof data.toolCallId !== 'string' || !data.toolCallId) return;
|
|
590
|
-
const id =
|
|
591
|
-
findExtAppEventNodeId(data) ?? findOnlyPendingExtAppNodeId(data.serverName, data.toolName);
|
|
572
|
+
const id = findExtAppEventNodeId(data) ?? findOnlyPendingExtAppNodeId(data.serverName, data.toolName);
|
|
592
573
|
if (!id) return;
|
|
593
574
|
if (nodes.value.has(id)) {
|
|
594
575
|
updateNodeData(id, { html: data.html });
|
|
@@ -597,8 +578,7 @@ function handleExtAppUpdate(data: Record<string, unknown>): void {
|
|
|
597
578
|
|
|
598
579
|
function handleExtAppResult(data: Record<string, unknown>): void {
|
|
599
580
|
if (typeof data.toolCallId !== 'string' || !data.toolCallId) return;
|
|
600
|
-
const id =
|
|
601
|
-
findExtAppEventNodeId(data) ?? findOnlyPendingExtAppNodeId(data.serverName, data.toolName);
|
|
581
|
+
const id = findExtAppEventNodeId(data) ?? findOnlyPendingExtAppNodeId(data.serverName, data.toolName);
|
|
602
582
|
if (!id) return;
|
|
603
583
|
if (nodes.value.has(id)) {
|
|
604
584
|
if (data.success === false) {
|
|
@@ -611,8 +591,7 @@ function handleExtAppResult(data: Record<string, unknown>): void {
|
|
|
611
591
|
success: typeof data.success === 'boolean' ? data.success : undefined,
|
|
612
592
|
error: typeof data.error === 'string' ? data.error : undefined,
|
|
613
593
|
content: typeof data.content === 'string' ? data.content : undefined,
|
|
614
|
-
detailedContent:
|
|
615
|
-
typeof data.detailedContent === 'string' ? data.detailedContent : undefined,
|
|
594
|
+
detailedContent: typeof data.detailedContent === 'string' ? data.detailedContent : undefined,
|
|
616
595
|
}),
|
|
617
596
|
});
|
|
618
597
|
}
|
|
@@ -843,14 +822,14 @@ function handleCanvasLayoutUpdate(data: Record<string, unknown>): void {
|
|
|
843
822
|
const shouldApplyViewport = !hasInitialServerLayout.value;
|
|
844
823
|
hasInitialServerLayout.value = true;
|
|
845
824
|
|
|
846
|
-
const serverNodes = layout.nodes
|
|
847
|
-
.map(parseCanvasNode)
|
|
848
|
-
.filter((node): node is CanvasNodeState => node !== null);
|
|
825
|
+
const serverNodes = layout.nodes.map(parseCanvasNode).filter((node): node is CanvasNodeState => node !== null);
|
|
849
826
|
const serverEdges = Array.isArray(layout.edges)
|
|
850
827
|
? layout.edges.map(parseCanvasEdge).filter((edge): edge is CanvasEdge => edge !== null)
|
|
851
828
|
: Array.from(edges.value.values());
|
|
852
829
|
const serverAnnotations = Array.isArray(layout.annotations)
|
|
853
|
-
? layout.annotations
|
|
830
|
+
? layout.annotations
|
|
831
|
+
.map(parseCanvasAnnotation)
|
|
832
|
+
.filter((annotation): annotation is CanvasAnnotation => annotation !== null)
|
|
854
833
|
: undefined;
|
|
855
834
|
const nextViewport = layout.viewport
|
|
856
835
|
? {
|
|
@@ -861,12 +840,15 @@ function handleCanvasLayoutUpdate(data: Record<string, unknown>): void {
|
|
|
861
840
|
: undefined;
|
|
862
841
|
|
|
863
842
|
cancelViewportAnimation();
|
|
864
|
-
applyServerCanvasLayout(
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
843
|
+
applyServerCanvasLayout(
|
|
844
|
+
{
|
|
845
|
+
...(nextViewport ? { viewport: nextViewport } : {}),
|
|
846
|
+
nodes: serverNodes,
|
|
847
|
+
edges: serverEdges,
|
|
848
|
+
...(serverAnnotations ? { annotations: serverAnnotations } : {}),
|
|
849
|
+
},
|
|
850
|
+
{ applyViewport: shouldApplyViewport },
|
|
851
|
+
);
|
|
870
852
|
|
|
871
853
|
syncAttentionFromSse({ event: 'canvas-layout-update', data });
|
|
872
854
|
}
|
|
@@ -923,9 +905,7 @@ function handleThemeChanged(data: Record<string, unknown>): void {
|
|
|
923
905
|
}
|
|
924
906
|
|
|
925
907
|
function handleContextPinsChanged(data: Record<string, unknown>): void {
|
|
926
|
-
const nodeIds = Array.isArray(data.nodeIds)
|
|
927
|
-
? data.nodeIds.filter((id): id is string => typeof id === 'string')
|
|
928
|
-
: [];
|
|
908
|
+
const nodeIds = Array.isArray(data.nodeIds) ? data.nodeIds.filter((id): id is string => typeof id === 'string') : [];
|
|
929
909
|
replaceContextPinsFromServer(nodeIds);
|
|
930
910
|
syncAttentionFromSse({ event: 'context-pins-changed', data });
|
|
931
911
|
}
|
|
@@ -939,7 +919,9 @@ function handleAxStateChanged(): void {
|
|
|
939
919
|
if (axRefreshTimer) clearTimeout(axRefreshTimer);
|
|
940
920
|
axRefreshTimer = setTimeout(() => {
|
|
941
921
|
axRefreshTimer = null;
|
|
942
|
-
void fetchAxSurfaceState().then((state) => {
|
|
922
|
+
void fetchAxSurfaceState().then((state) => {
|
|
923
|
+
axSurfaceState.value = state;
|
|
924
|
+
});
|
|
943
925
|
}, 150);
|
|
944
926
|
}
|
|
945
927
|
|
|
@@ -951,11 +933,12 @@ function handleAxIntent(data: Record<string, unknown>): void {
|
|
|
951
933
|
// be pruned if its `clear` frame were dropped. The server always sets it, so this
|
|
952
934
|
// only rejects a malformed frame — keeping the backstop's guarantee real.
|
|
953
935
|
if (
|
|
954
|
-
!intent
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
)
|
|
936
|
+
!intent ||
|
|
937
|
+
typeof intent.id !== 'string' ||
|
|
938
|
+
typeof intent.kind !== 'string' ||
|
|
939
|
+
typeof intent.expiresAt !== 'number'
|
|
940
|
+
)
|
|
941
|
+
return;
|
|
959
942
|
upsertIntent(intent);
|
|
960
943
|
}
|
|
961
944
|
|