pmx-canvas 0.1.35 → 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/CHANGELOG.md +461 -0
- package/Readme.md +14 -2
- package/dist/canvas/index.js +82 -41
- package/dist/json-render/index.js +89 -334
- package/dist/types/client/nodes/ExtAppFrame.d.ts +2 -0
- package/dist/types/mcp/canvas-access.d.ts +12 -159
- package/dist/types/server/ax-context.d.ts +1 -1
- package/dist/types/server/ax-state-manager.d.ts +256 -0
- package/dist/types/server/ax-state.d.ts +29 -1
- package/dist/types/server/ax-wait.d.ts +23 -0
- package/dist/types/server/canvas-operations.d.ts +1 -12
- package/dist/types/server/canvas-state.d.ts +46 -14
- package/dist/types/server/html-surface.d.ts +7 -0
- package/dist/types/server/index.d.ts +66 -26
- package/dist/types/server/operations/composites.d.ts +121 -0
- package/dist/types/server/operations/http.d.ts +7 -0
- package/dist/types/server/operations/index.d.ts +8 -0
- package/dist/types/server/operations/invoker.d.ts +13 -0
- package/dist/types/server/operations/mcp.d.ts +15 -0
- package/dist/types/server/operations/ops/annotation.d.ts +2 -0
- package/dist/types/server/operations/ops/app.d.ts +33 -0
- package/dist/types/server/operations/ops/ax-await.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-shared.d.ts +31 -0
- package/dist/types/server/operations/ops/ax-state.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-timeline.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-work.d.ts +2 -0
- package/dist/types/server/operations/ops/batch.d.ts +19 -0
- package/dist/types/server/operations/ops/edges.d.ts +2 -0
- package/dist/types/server/operations/ops/groups.d.ts +2 -0
- package/dist/types/server/operations/ops/json-render.d.ts +31 -0
- package/dist/types/server/operations/ops/nodes.d.ts +62 -0
- package/dist/types/server/operations/ops/query.d.ts +2 -0
- package/dist/types/server/operations/ops/snapshots.d.ts +2 -0
- package/dist/types/server/operations/ops/validate.d.ts +2 -0
- package/dist/types/server/operations/ops/viewport.d.ts +2 -0
- package/dist/types/server/operations/ops/webview.d.ts +2 -0
- package/dist/types/server/operations/registry.d.ts +15 -0
- package/dist/types/server/operations/types.d.ts +116 -0
- package/dist/types/server/operations/webview-runner.d.ts +69 -0
- package/docs/RELEASE.md +5 -0
- package/docs/adr-001-bun-only-runtime.md +46 -0
- package/docs/api-stability.md +57 -0
- package/docs/ax-host-adapter-contract.md +65 -0
- package/docs/ax-state-contract.md +72 -0
- package/docs/http-api.md +34 -2
- package/docs/mcp.md +64 -11
- package/docs/plans/plan-005-operation-registry.md +84 -0
- package/docs/plans/plan-006-mcp-tool-consolidation.md +109 -0
- package/docs/plans/plan-007-ax-domain.md +99 -0
- package/docs/plans/plan-008-registry-finish.md +91 -0
- package/docs/screenshot.png +0 -0
- package/docs/tech-debt-assessment-2026-06.md +90 -0
- package/package.json +3 -3
- package/skills/pmx-canvas/SKILL.md +233 -185
- package/skills/pmx-canvas/evals/evals.json +3 -3
- package/skills/pmx-canvas/references/codex-app-adapter.md +24 -11
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +31 -1
- package/src/cli/agent.ts +52 -31
- package/src/client/nodes/ExtAppFrame.tsx +73 -5
- package/src/client/nodes/HtmlNode.tsx +12 -3
- package/src/client/nodes/McpAppNode.tsx +12 -3
- package/src/json-render/renderer/index.tsx +3 -0
- package/src/mcp/canvas-access.ts +43 -774
- package/src/mcp/server.ts +190 -2001
- package/src/server/ax-context.ts +7 -1
- package/src/server/ax-state-manager.ts +808 -0
- package/src/server/ax-state.ts +89 -2
- package/src/server/ax-wait.ts +56 -0
- package/src/server/canvas-operations.ts +2 -328
- package/src/server/canvas-schema.ts +2 -2
- package/src/server/canvas-state.ts +140 -382
- package/src/server/html-surface.ts +49 -11
- package/src/server/index.ts +136 -192
- package/src/server/operations/composites.ts +355 -0
- package/src/server/operations/http.ts +103 -0
- package/src/server/operations/index.ts +65 -0
- package/src/server/operations/invoker.ts +87 -0
- package/src/server/operations/mcp.ts +221 -0
- package/src/server/operations/ops/annotation.ts +60 -0
- package/src/server/operations/ops/app.ts +447 -0
- package/src/server/operations/ops/ax-await.ts +216 -0
- package/src/server/operations/ops/ax-shared.ts +38 -0
- package/src/server/operations/ops/ax-state.ts +249 -0
- package/src/server/operations/ops/ax-timeline.ts +381 -0
- package/src/server/operations/ops/ax-work.ts +635 -0
- package/src/server/operations/ops/batch.ts +365 -0
- package/src/server/operations/ops/edges.ts +166 -0
- package/src/server/operations/ops/groups.ts +176 -0
- package/src/server/operations/ops/json-render.ts +691 -0
- package/src/server/operations/ops/nodes.ts +1047 -0
- package/src/server/operations/ops/query.ts +281 -0
- package/src/server/operations/ops/snapshots.ts +366 -0
- package/src/server/operations/ops/validate.ts +37 -0
- package/src/server/operations/ops/viewport.ts +219 -0
- package/src/server/operations/ops/webview.ts +339 -0
- package/src/server/operations/registry.ts +79 -0
- package/src/server/operations/types.ts +150 -0
- package/src/server/operations/webview-runner.ts +77 -0
- package/src/server/server.ts +253 -2170
- package/src/server/web-artifacts.ts +6 -2
|
@@ -108,22 +108,60 @@ function injectIntoHead(html: string, content: string): string {
|
|
|
108
108
|
* injected when the node's AX capabilities are enabled (opt-in for `html`), and
|
|
109
109
|
* the server re-validates every interaction — so this is a convenience surface,
|
|
110
110
|
* not a trust boundary.
|
|
111
|
+
*
|
|
112
|
+
* `emit` returns a Promise that resolves with the interaction result once the
|
|
113
|
+
* parent acks it (report #55 — built-in confirmation so a click no longer looks
|
|
114
|
+
* like "nothing happened"). Authors can also `window.PMX_AX.on('ack', cb)` or
|
|
115
|
+
* listen for the `pmx-ax-ack` CustomEvent. Resolves with an `ax-ack-timeout`
|
|
116
|
+
* result after 10s if no ack arrives (e.g. an older parent), so `await emit()`
|
|
117
|
+
* never hangs.
|
|
111
118
|
*/
|
|
112
119
|
export function buildAxBridge(axToken: string, nodeId: string): string {
|
|
113
120
|
const token = JSON.stringify(axToken);
|
|
114
121
|
const node = JSON.stringify(nodeId);
|
|
115
122
|
return `<script data-pmx-canvas-ax-bridge>
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
window.PMX_AX
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
(function () {
|
|
124
|
+
const PMX_AX_TOKEN = ${token};
|
|
125
|
+
const PMX_AX_NODE_ID = ${node};
|
|
126
|
+
window.PMX_AX = window.PMX_AX || {};
|
|
127
|
+
const pending = new Map();
|
|
128
|
+
const ackListeners = [];
|
|
129
|
+
let seq = 0;
|
|
130
|
+
window.PMX_AX.emit = function (type, payload) {
|
|
131
|
+
seq += 1;
|
|
132
|
+
const correlationId = PMX_AX_NODE_ID + '-' + seq + '-' + (Date.now ? Date.now() : 0);
|
|
133
|
+
window.parent.postMessage({
|
|
134
|
+
source: 'pmx-canvas-ax',
|
|
135
|
+
token: PMX_AX_TOKEN,
|
|
136
|
+
nodeId: PMX_AX_NODE_ID,
|
|
137
|
+
correlationId: correlationId,
|
|
138
|
+
interaction: { type: String(type), payload: payload && typeof payload === 'object' ? payload : {} },
|
|
139
|
+
}, '*');
|
|
140
|
+
return new Promise(function (resolve) {
|
|
141
|
+
const timer = setTimeout(function () {
|
|
142
|
+
pending.delete(correlationId);
|
|
143
|
+
// Match the real reject shape ({ ok:false, status, code:string, error }) so a
|
|
144
|
+
// surface inspecting r.code/r.status sees a consistent type on timeout vs reject.
|
|
145
|
+
resolve({ ok: false, status: 504, code: 'ax-ack-timeout', error: 'ax-ack-timeout' });
|
|
146
|
+
}, 10000);
|
|
147
|
+
pending.set(correlationId, function (result) { clearTimeout(timer); resolve(result); });
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
window.PMX_AX.on = function (eventType, cb) {
|
|
151
|
+
if (eventType === 'ack' && typeof cb === 'function') ackListeners.push(cb);
|
|
152
|
+
};
|
|
153
|
+
window.addEventListener('message', function (event) {
|
|
154
|
+
const m = event.data;
|
|
155
|
+
if (!m || m.source !== 'pmx-canvas-ax-ack' || m.token !== PMX_AX_TOKEN) return;
|
|
156
|
+
const result = m.result || { ok: false };
|
|
157
|
+
const resolver = m.correlationId ? pending.get(m.correlationId) : undefined;
|
|
158
|
+
if (resolver) { pending.delete(m.correlationId); resolver(result); }
|
|
159
|
+
for (let i = 0; i < ackListeners.length; i += 1) {
|
|
160
|
+
try { ackListeners[i](result, m.interaction || null); } catch (e) {}
|
|
161
|
+
}
|
|
162
|
+
try { window.dispatchEvent(new CustomEvent('pmx-ax-ack', { detail: { result: result, interaction: m.interaction || null } })); } catch (e) {}
|
|
163
|
+
});
|
|
164
|
+
})();
|
|
127
165
|
</script>`;
|
|
128
166
|
}
|
|
129
167
|
|
package/src/server/index.ts
CHANGED
|
@@ -3,7 +3,9 @@ import { canvasState, IMAGE_MIME_MAP } from './canvas-state.js';
|
|
|
3
3
|
import type { CanvasAnnotation, CanvasNodeState, CanvasEdge, CanvasLayout, ViewportState } from './canvas-state.js';
|
|
4
4
|
import { buildCanvasAxContext } from './ax-context.js';
|
|
5
5
|
import { applyAxInteraction, type AxInteractionInput, type AxInteractionPublicResult } from './ax-interaction.js';
|
|
6
|
+
import { waitForAxResolution } from './ax-wait.js';
|
|
6
7
|
import type {
|
|
8
|
+
PmxAxActivityKind,
|
|
7
9
|
PmxAxApprovalGate,
|
|
8
10
|
PmxAxCommandDescriptor,
|
|
9
11
|
PmxAxContext,
|
|
@@ -29,7 +31,6 @@ import type {
|
|
|
29
31
|
PmxAxWorkItemStatus,
|
|
30
32
|
} from './ax-state.js';
|
|
31
33
|
import type { AxTimelineQuery } from './canvas-db.js';
|
|
32
|
-
import { findCanvasExtAppNodeId } from './ext-app-lookup.js';
|
|
33
34
|
import { onFileNodeChanged } from './file-watcher.js';
|
|
34
35
|
import { findOpenCanvasPosition, computeGroupBounds } from './placement.js';
|
|
35
36
|
import { searchNodes, buildSpatialContext } from './spatial-analysis.js';
|
|
@@ -38,27 +39,18 @@ import { recomputeCodeGraph, buildCodeGraphSummary, formatCodeGraph } from './co
|
|
|
38
39
|
import {
|
|
39
40
|
addCanvasNode,
|
|
40
41
|
addCanvasEdge,
|
|
41
|
-
appendCanvasJsonRenderStream,
|
|
42
|
-
createCanvasStreamingJsonRenderNode,
|
|
43
|
-
MARKDOWN_NODE_DEFAULT_SIZE,
|
|
44
|
-
MCP_APP_NODE_DEFAULT_SIZE,
|
|
45
|
-
IMAGE_NODE_DEFAULT_SIZE,
|
|
46
|
-
LEDGER_NODE_DEFAULT_SIZE,
|
|
47
42
|
applyCanvasNodeUpdates,
|
|
48
43
|
arrangeCanvasNodes,
|
|
49
44
|
clearCanvas,
|
|
50
45
|
createCanvasGraphNode,
|
|
51
46
|
createCanvasGroup,
|
|
52
47
|
createCanvasJsonRenderNode,
|
|
53
|
-
buildStructuredNodeUpdate,
|
|
54
48
|
fitCanvasView,
|
|
55
49
|
deleteCanvasSnapshot,
|
|
56
|
-
executeCanvasBatch,
|
|
57
50
|
gcCanvasSnapshots,
|
|
58
51
|
groupCanvasNodes,
|
|
59
52
|
listCanvasSnapshots,
|
|
60
53
|
refreshCanvasWebpageNode,
|
|
61
|
-
removeCanvasNode,
|
|
62
54
|
removeCanvasEdge,
|
|
63
55
|
resolveHtmlContent,
|
|
64
56
|
restoreCanvasSnapshot,
|
|
@@ -67,11 +59,19 @@ import {
|
|
|
67
59
|
syncCanvasRuntimeBackends,
|
|
68
60
|
setCanvasContextPins,
|
|
69
61
|
ungroupCanvasNodes,
|
|
70
|
-
validateCanvasNodePatch,
|
|
71
|
-
hasStructuredNodeUpdateFields,
|
|
72
|
-
hasTraceNodeDataFields,
|
|
73
|
-
mergeTraceNodeDataFields,
|
|
74
62
|
} from './canvas-operations.js';
|
|
63
|
+
import {
|
|
64
|
+
buildNodePatch,
|
|
65
|
+
createBasicCanvasNode,
|
|
66
|
+
removeNodeCore,
|
|
67
|
+
setGroupChildrenFromApi,
|
|
68
|
+
} from './operations/ops/nodes.js';
|
|
69
|
+
import { streamJsonRenderCore } from './operations/ops/json-render.js';
|
|
70
|
+
import {
|
|
71
|
+
executeOperation,
|
|
72
|
+
runCanvasBatchOperation,
|
|
73
|
+
type OpenMcpAppCoreResult,
|
|
74
|
+
} from './operations/index.js';
|
|
75
75
|
import { validateCanvasLayout } from './canvas-validation.js';
|
|
76
76
|
import { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
|
|
77
77
|
import { serializeCanvasNode, type SerializedCanvasNode } from './canvas-serialization.js';
|
|
@@ -84,13 +84,9 @@ import {
|
|
|
84
84
|
} from './web-artifacts.js';
|
|
85
85
|
import {
|
|
86
86
|
closeMcpAppSession,
|
|
87
|
-
openMcpApp as openExternalMcpApp,
|
|
88
87
|
type ExternalMcpTransportConfig,
|
|
89
88
|
} from './mcp-app-runtime.js';
|
|
90
89
|
import {
|
|
91
|
-
buildExcalidrawOpenMcpAppInput,
|
|
92
|
-
ensureExcalidrawCheckpointId,
|
|
93
|
-
isExcalidrawCreateView,
|
|
94
90
|
type DiagramPresetOpenInput,
|
|
95
91
|
} from './diagram-presets.js';
|
|
96
92
|
import {
|
|
@@ -257,29 +253,9 @@ export class PmxCanvas extends EventEmitter {
|
|
|
257
253
|
if (!groupNode) throw new Error(`Group node "${groupId}" was not created.`);
|
|
258
254
|
return toSdkNode(groupNode);
|
|
259
255
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
? MARKDOWN_NODE_DEFAULT_SIZE.width
|
|
264
|
-
: input.type === 'mcp-app'
|
|
265
|
-
? MCP_APP_NODE_DEFAULT_SIZE.width
|
|
266
|
-
: input.type === 'image'
|
|
267
|
-
? IMAGE_NODE_DEFAULT_SIZE.width
|
|
268
|
-
: input.type === 'ledger'
|
|
269
|
-
? LEDGER_NODE_DEFAULT_SIZE.width
|
|
270
|
-
: 360,
|
|
271
|
-
defaultHeight: input.type === 'markdown'
|
|
272
|
-
? MARKDOWN_NODE_DEFAULT_SIZE.height
|
|
273
|
-
: input.type === 'mcp-app'
|
|
274
|
-
? MCP_APP_NODE_DEFAULT_SIZE.height
|
|
275
|
-
: input.type === 'image'
|
|
276
|
-
? IMAGE_NODE_DEFAULT_SIZE.height
|
|
277
|
-
: input.type === 'ledger'
|
|
278
|
-
? LEDGER_NODE_DEFAULT_SIZE.height
|
|
279
|
-
: 200,
|
|
280
|
-
fileMode: 'path',
|
|
281
|
-
...(input.strictSize ? { strictSize: true } : {}),
|
|
282
|
-
});
|
|
256
|
+
// Thin wrapper over the shared operation core (plan-005); the SDK keeps
|
|
257
|
+
// fileMode 'path' as an explicit visible parameter instead of forked code.
|
|
258
|
+
const { node, needsCodeGraphRecompute } = createBasicCanvasNode(input, { fileMode: 'path' });
|
|
283
259
|
|
|
284
260
|
emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
|
|
285
261
|
|
|
@@ -289,8 +265,6 @@ export class PmxCanvas extends EventEmitter {
|
|
|
289
265
|
});
|
|
290
266
|
}
|
|
291
267
|
|
|
292
|
-
const node = canvasState.getNode(id);
|
|
293
|
-
if (!node) throw new Error(`Node "${id}" was not created.`);
|
|
294
268
|
return toSdkNode(node);
|
|
295
269
|
}
|
|
296
270
|
|
|
@@ -337,58 +311,18 @@ export class PmxCanvas extends EventEmitter {
|
|
|
337
311
|
updateNode(id: string, patch: Partial<CanvasNodeState> & Record<string, unknown>): void {
|
|
338
312
|
const existing = canvasState.getNode(id);
|
|
339
313
|
if (!existing) return;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
if (patch.pinned !== undefined) resolvedPatch.pinned = patch.pinned;
|
|
345
|
-
if (patch.dockPosition !== undefined) resolvedPatch.dockPosition = patch.dockPosition;
|
|
346
|
-
|
|
347
|
-
if (hasStructuredNodeUpdateFields(patch)) {
|
|
348
|
-
resolvedPatch.data = buildStructuredNodeUpdate(existing, patch).data;
|
|
349
|
-
} else if (
|
|
350
|
-
patch.data !== undefined ||
|
|
351
|
-
patch.title !== undefined ||
|
|
352
|
-
patch.content !== undefined ||
|
|
353
|
-
typeof patch.arrangeLocked === 'boolean' ||
|
|
354
|
-
typeof patch.strictSize === 'boolean' ||
|
|
355
|
-
(existing.type === 'trace' && hasTraceNodeDataFields(patch))
|
|
356
|
-
) {
|
|
357
|
-
const nextData = {
|
|
358
|
-
...existing.data,
|
|
359
|
-
...(patch.data && typeof patch.data === 'object' && !Array.isArray(patch.data) ? patch.data : {}),
|
|
360
|
-
...(typeof patch.title === 'string' ? { title: patch.title } : {}),
|
|
361
|
-
...(typeof patch.content === 'string' ? { content: patch.content } : {}),
|
|
362
|
-
...(typeof patch.arrangeLocked === 'boolean' ? { arrangeLocked: patch.arrangeLocked } : {}),
|
|
363
|
-
...(typeof patch.strictSize === 'boolean' ? { strictSize: patch.strictSize } : {}),
|
|
364
|
-
};
|
|
365
|
-
resolvedPatch.data = existing.type === 'trace'
|
|
366
|
-
? mergeTraceNodeDataFields(nextData, patch)
|
|
367
|
-
: nextData;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const error = validateCanvasNodePatch({
|
|
371
|
-
...(resolvedPatch.position ? { position: resolvedPatch.position } : {}),
|
|
372
|
-
...(resolvedPatch.size ? { size: resolvedPatch.size } : {}),
|
|
373
|
-
});
|
|
374
|
-
if (error) {
|
|
375
|
-
throw new Error(error);
|
|
376
|
-
}
|
|
314
|
+
// Thin wrapper over the shared patch core (plan-005): the SDK now carries
|
|
315
|
+
// the same superset semantics as HTTP/MCP (webpage titleSource/url, html
|
|
316
|
+
// top-level fields, axCapabilities merge, group children).
|
|
317
|
+
const { patch: resolvedPatch, groupChildIds } = buildNodePatch(existing, patch);
|
|
377
318
|
canvasState.updateNode(id, resolvedPatch);
|
|
319
|
+
if (groupChildIds !== undefined) setGroupChildrenFromApi(id, groupChildIds);
|
|
378
320
|
emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
|
|
379
321
|
}
|
|
380
322
|
|
|
323
|
+
/** Remove a node. Missing id throws (plan-005 unifies this across surfaces). */
|
|
381
324
|
removeNode(id: string): void {
|
|
382
|
-
const
|
|
383
|
-
const appSessionId =
|
|
384
|
-
existing?.type === 'mcp-app' && typeof existing.data.appSessionId === 'string'
|
|
385
|
-
? existing.data.appSessionId
|
|
386
|
-
: null;
|
|
387
|
-
if (appSessionId) {
|
|
388
|
-
closeMcpAppSession(appSessionId);
|
|
389
|
-
}
|
|
390
|
-
const { removed, needsCodeGraphRecompute } = removeCanvasNode(id);
|
|
391
|
-
if (!removed) return;
|
|
325
|
+
const { needsCodeGraphRecompute } = removeNodeCore(id);
|
|
392
326
|
emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
|
|
393
327
|
|
|
394
328
|
if (needsCodeGraphRecompute) {
|
|
@@ -512,8 +446,8 @@ export class PmxCanvas extends EventEmitter {
|
|
|
512
446
|
return canvasState.getAxState();
|
|
513
447
|
}
|
|
514
448
|
|
|
515
|
-
getAxContext(): PmxAxContext {
|
|
516
|
-
return buildCanvasAxContext();
|
|
449
|
+
getAxContext(options?: { consumer?: string }): PmxAxContext {
|
|
450
|
+
return buildCanvasAxContext(options?.consumer);
|
|
517
451
|
}
|
|
518
452
|
|
|
519
453
|
setAxFocus(nodeIds: string[], options?: { source?: PmxAxSource }): PmxAxFocusState {
|
|
@@ -710,6 +644,84 @@ export class PmxCanvas extends EventEmitter {
|
|
|
710
644
|
return modeRequest;
|
|
711
645
|
}
|
|
712
646
|
|
|
647
|
+
// ── Activity ingestion (primitive A — bidirectional board) ────────
|
|
648
|
+
ingestActivity(
|
|
649
|
+
input: {
|
|
650
|
+
kind: PmxAxActivityKind;
|
|
651
|
+
title: string;
|
|
652
|
+
summary?: string | null;
|
|
653
|
+
outcome?: 'success' | 'failure';
|
|
654
|
+
ref?: string | null;
|
|
655
|
+
nodeIds?: string[];
|
|
656
|
+
data?: Record<string, unknown> | null;
|
|
657
|
+
reactions?: {
|
|
658
|
+
workItem?: false | { status?: PmxAxWorkItemStatus; detail?: string | null };
|
|
659
|
+
evidence?: false | { kind?: PmxAxEvidenceKind; body?: string | null };
|
|
660
|
+
review?: false | { severity?: PmxAxReviewSeverity; kind?: PmxAxReviewKind; anchorType?: PmxAxReviewAnchorType; nodeId?: string | null };
|
|
661
|
+
};
|
|
662
|
+
},
|
|
663
|
+
options?: { source?: PmxAxSource },
|
|
664
|
+
): { event: PmxAxEvent; workItem: PmxAxWorkItem | null; evidence: PmxAxEvidence | null; review: PmxAxReviewAnnotation | null } {
|
|
665
|
+
const result = canvasState.ingestActivity(input, { source: options?.source ?? 'sdk' });
|
|
666
|
+
emitPrimaryWorkbenchEvent('ax-event-created', { event: result.event });
|
|
667
|
+
if (result.workItem) emitPrimaryWorkbenchEvent('ax-state-changed', { workItem: result.workItem });
|
|
668
|
+
if (result.evidence) emitPrimaryWorkbenchEvent('ax-event-created', { evidence: result.evidence });
|
|
669
|
+
if (result.review) emitPrimaryWorkbenchEvent('ax-state-changed', { reviewAnnotation: result.review });
|
|
670
|
+
return result;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// ── Single-item readers + blocking waits (primitive D — gates that gate) ──
|
|
674
|
+
getApproval(id: string): PmxAxApprovalGate | null {
|
|
675
|
+
return canvasState.getApproval(id);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
getElicitation(id: string): PmxAxElicitation | null {
|
|
679
|
+
return canvasState.getElicitation(id);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
getModeRequest(id: string): PmxAxModeRequest | null {
|
|
683
|
+
return canvasState.getModeRequest(id);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
async awaitApproval(
|
|
687
|
+
id: string,
|
|
688
|
+
options?: { timeoutMs?: number; signal?: AbortSignal },
|
|
689
|
+
): Promise<{ approvalGate: PmxAxApprovalGate | null; pending: boolean }> {
|
|
690
|
+
const { value, pending } = await waitForAxResolution<PmxAxApprovalGate>({
|
|
691
|
+
read: () => canvasState.getApproval(id),
|
|
692
|
+
isResolved: (g) => g.status !== 'pending',
|
|
693
|
+
timeoutMs: options?.timeoutMs ?? 30000,
|
|
694
|
+
...(options?.signal ? { signal: options.signal } : {}),
|
|
695
|
+
});
|
|
696
|
+
return { approvalGate: value, pending };
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
async awaitElicitation(
|
|
700
|
+
id: string,
|
|
701
|
+
options?: { timeoutMs?: number; signal?: AbortSignal },
|
|
702
|
+
): Promise<{ elicitation: PmxAxElicitation | null; pending: boolean }> {
|
|
703
|
+
const { value, pending } = await waitForAxResolution<PmxAxElicitation>({
|
|
704
|
+
read: () => canvasState.getElicitation(id),
|
|
705
|
+
isResolved: (e) => e.status !== 'pending',
|
|
706
|
+
timeoutMs: options?.timeoutMs ?? 30000,
|
|
707
|
+
...(options?.signal ? { signal: options.signal } : {}),
|
|
708
|
+
});
|
|
709
|
+
return { elicitation: value, pending };
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
async awaitMode(
|
|
713
|
+
id: string,
|
|
714
|
+
options?: { timeoutMs?: number; signal?: AbortSignal },
|
|
715
|
+
): Promise<{ modeRequest: PmxAxModeRequest | null; pending: boolean }> {
|
|
716
|
+
const { value, pending } = await waitForAxResolution<PmxAxModeRequest>({
|
|
717
|
+
read: () => canvasState.getModeRequest(id),
|
|
718
|
+
isResolved: (m) => m.status !== 'pending',
|
|
719
|
+
timeoutMs: options?.timeoutMs ?? 30000,
|
|
720
|
+
...(options?.signal ? { signal: options.signal } : {}),
|
|
721
|
+
});
|
|
722
|
+
return { modeRequest: value, pending };
|
|
723
|
+
}
|
|
724
|
+
|
|
713
725
|
getCommandRegistry(): PmxAxCommandDescriptor[] {
|
|
714
726
|
return canvasState.getCommandRegistry();
|
|
715
727
|
}
|
|
@@ -850,13 +862,6 @@ export class PmxCanvas extends EventEmitter {
|
|
|
850
862
|
return validateCanvasLayout(canvasState.getLayout());
|
|
851
863
|
}
|
|
852
864
|
|
|
853
|
-
private findCanvasExtAppNodeId(toolCallId: string): string | null {
|
|
854
|
-
return findCanvasExtAppNodeId(toolCallId, {
|
|
855
|
-
getNode: (id) => canvasState.getNode(id),
|
|
856
|
-
listNodes: () => canvasState.getLayout().nodes,
|
|
857
|
-
});
|
|
858
|
-
}
|
|
859
|
-
|
|
860
865
|
describeSchema() {
|
|
861
866
|
return describeCanvasSchema();
|
|
862
867
|
}
|
|
@@ -870,14 +875,22 @@ export class PmxCanvas extends EventEmitter {
|
|
|
870
875
|
}
|
|
871
876
|
|
|
872
877
|
async runBatch(operations: Array<{ op: string; assign?: string; args?: Record<string, unknown> }>) {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
878
|
+
// Delegates to the canvas.batch registry meta-op (plan-008 Wave 2). The op
|
|
879
|
+
// emits the single final canvas-layout-update itself (via the registry
|
|
880
|
+
// emitter, which server.ts wires to emitPrimaryWorkbenchEvent) — do NOT
|
|
881
|
+
// emit again here or the frame would fire twice.
|
|
882
|
+
return await runCanvasBatchOperation(operations);
|
|
876
883
|
}
|
|
877
884
|
|
|
878
885
|
async buildWebArtifact(
|
|
879
|
-
input: WebArtifactBuildInput & { openInCanvas?: boolean },
|
|
886
|
+
input: WebArtifactBuildInput & { openInCanvas?: boolean; includeLogs?: boolean },
|
|
880
887
|
): Promise<WebArtifactCanvasBuildResult> {
|
|
888
|
+
// The registry's webartifact.build op wraps buildWebArtifactOnCanvas and
|
|
889
|
+
// returns a wire ENVELOPE (path/bytes/…); the SDK's documented return is the
|
|
890
|
+
// full WebArtifactCanvasBuildResult, so the SDK calls the build runtime
|
|
891
|
+
// directly here (the op core is the same buildWebArtifactOnCanvas; the node
|
|
892
|
+
// creation emits its own canvas-layout-update). The op and the SDK share the
|
|
893
|
+
// single build runtime — no behavior divergence.
|
|
881
894
|
return buildWebArtifactOnCanvas(input);
|
|
882
895
|
}
|
|
883
896
|
|
|
@@ -893,75 +906,21 @@ export class PmxCanvas extends EventEmitter {
|
|
|
893
906
|
width?: number;
|
|
894
907
|
height?: number;
|
|
895
908
|
timeoutMs?: number;
|
|
896
|
-
}): Promise<
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
const opened = await openExternalMcpApp({
|
|
906
|
-
transport: input.transport,
|
|
907
|
-
toolName: input.toolName,
|
|
908
|
-
...(input.toolArguments ? { toolArguments: input.toolArguments } : {}),
|
|
909
|
-
...(input.serverName ? { serverName: input.serverName } : {}),
|
|
910
|
-
...(typeof input.timeoutMs === 'number' ? { timeoutMs: input.timeoutMs } : {}),
|
|
911
|
-
});
|
|
912
|
-
const toolCallId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
913
|
-
const previousSessionId = targetNode?.data.appSessionId;
|
|
914
|
-
if (typeof previousSessionId === 'string' && previousSessionId.trim().length > 0) {
|
|
915
|
-
closeMcpAppSession(previousSessionId);
|
|
916
|
-
}
|
|
917
|
-
const nodeIdSeed = input.nodeId ?? `ext-app-${toolCallId}`;
|
|
918
|
-
const toolResult = isExcalidrawCreateView(opened.serverName, opened.toolName)
|
|
919
|
-
? ensureExcalidrawCheckpointId(opened.toolResult, nodeIdSeed)
|
|
920
|
-
: opened.toolResult;
|
|
921
|
-
emitPrimaryWorkbenchEvent('ext-app-open', {
|
|
922
|
-
toolCallId,
|
|
923
|
-
nodeId: nodeIdSeed,
|
|
924
|
-
title: input.title ?? opened.tool.title ?? opened.tool.name,
|
|
925
|
-
html: opened.html,
|
|
926
|
-
toolInput: opened.toolInput,
|
|
927
|
-
serverName: opened.serverName,
|
|
928
|
-
toolName: opened.toolName,
|
|
929
|
-
appSessionId: opened.sessionId,
|
|
930
|
-
transportConfig: input.transport,
|
|
931
|
-
resourceUri: opened.resourceUri,
|
|
932
|
-
toolDefinition: opened.tool,
|
|
933
|
-
sessionStatus: 'ready',
|
|
934
|
-
sessionError: null,
|
|
935
|
-
...(opened.resourceMeta ? { resourceMeta: opened.resourceMeta } : {}),
|
|
936
|
-
...(typeof input.x === 'number' ? { x: input.x } : {}),
|
|
937
|
-
...(typeof input.y === 'number' ? { y: input.y } : {}),
|
|
938
|
-
...(typeof input.width === 'number' ? { width: input.width } : {}),
|
|
939
|
-
...(typeof input.height === 'number' ? { height: input.height } : {}),
|
|
940
|
-
});
|
|
941
|
-
emitPrimaryWorkbenchEvent('ext-app-result', {
|
|
942
|
-
toolCallId,
|
|
943
|
-
nodeId: nodeIdSeed,
|
|
944
|
-
serverName: opened.serverName,
|
|
945
|
-
toolName: opened.toolName,
|
|
946
|
-
success: toolResult.isError !== true,
|
|
947
|
-
result: toolResult,
|
|
948
|
-
});
|
|
949
|
-
const nodeId = input.nodeId ?? this.findCanvasExtAppNodeId(toolCallId);
|
|
950
|
-
return {
|
|
951
|
-
ok: true,
|
|
952
|
-
...(nodeId ? { id: nodeId } : {}),
|
|
953
|
-
nodeId,
|
|
954
|
-
toolCallId,
|
|
955
|
-
sessionId: opened.sessionId,
|
|
956
|
-
resourceUri: opened.resourceUri,
|
|
957
|
-
};
|
|
909
|
+
}): Promise<OpenMcpAppCoreResult> {
|
|
910
|
+
// Delegate to the mcpapp.open registry op (plan-008 Wave 4). The op handler
|
|
911
|
+
// is the relocated legacy body (toolCallId, openExternalMcpApp, prior-session
|
|
912
|
+
// close, ext-app-open + ext-app-result via ctx.emit → the registry emitter
|
|
913
|
+
// wired to emitPrimaryWorkbenchEvent). mutates:false, so the registry adds no
|
|
914
|
+
// canvas-layout-update; the two ext-app-* frames fire exactly once.
|
|
915
|
+
return await executeOperation('mcpapp.open', input) as OpenMcpAppCoreResult;
|
|
958
916
|
}
|
|
959
917
|
|
|
960
918
|
async addDiagram(
|
|
961
919
|
input: DiagramPresetOpenInput,
|
|
962
|
-
): Promise<
|
|
963
|
-
|
|
964
|
-
|
|
920
|
+
): Promise<OpenMcpAppCoreResult> {
|
|
921
|
+
// Delegate to the diagram.open registry op, which builds the Excalidraw
|
|
922
|
+
// OpenMcpApp input and dispatches to the shared open core (one ext-app-* pair).
|
|
923
|
+
return await executeOperation('diagram.open', input) as OpenMcpAppCoreResult;
|
|
965
924
|
}
|
|
966
925
|
|
|
967
926
|
addJsonRenderNode(
|
|
@@ -997,32 +956,17 @@ export class PmxCanvas extends EventEmitter {
|
|
|
997
956
|
elementCount: number;
|
|
998
957
|
streamStatus: 'open' | 'closed';
|
|
999
958
|
} {
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
...(input.width !== undefined ? { width: input.width } : {}),
|
|
1008
|
-
...(input.height !== undefined ? { height: input.height } : {}),
|
|
1009
|
-
...(input.strictSize ? { strictSize: true } : {}),
|
|
1010
|
-
});
|
|
1011
|
-
nodeId = created.id;
|
|
1012
|
-
url = created.url;
|
|
1013
|
-
} else {
|
|
1014
|
-
url = String(canvasState.getNode(nodeId)?.data.url ?? '');
|
|
1015
|
-
}
|
|
1016
|
-
const result = appendCanvasJsonRenderStream(
|
|
1017
|
-
nodeId,
|
|
1018
|
-
Array.isArray(input.patches) ? input.patches : [],
|
|
1019
|
-
input.done === true,
|
|
1020
|
-
);
|
|
1021
|
-
if (!result.ok) throw new Error(result.error);
|
|
959
|
+
// Thin wrapper over the shared create-or-append core (plan-005). The op
|
|
960
|
+
// handler and this SDK method now share one implementation; the SDK emits
|
|
961
|
+
// the layout update itself (it does not flow through the registry's
|
|
962
|
+
// `mutates` path). `streamJsonRenderCore` throws OperationError (an Error
|
|
963
|
+
// subclass with the same message) on a bad append target. The core's
|
|
964
|
+
// result carries an extra `ok: true`; the SDK's wire shape omits it.
|
|
965
|
+
const result = streamJsonRenderCore(input);
|
|
1022
966
|
emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
|
|
1023
967
|
return {
|
|
1024
|
-
id:
|
|
1025
|
-
url,
|
|
968
|
+
id: result.id,
|
|
969
|
+
url: result.url,
|
|
1026
970
|
applied: result.applied,
|
|
1027
971
|
skipped: result.skipped,
|
|
1028
972
|
specVersion: result.specVersion,
|