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
package/src/mcp/canvas-access.ts
CHANGED
|
@@ -10,11 +10,7 @@ import {
|
|
|
10
10
|
type PmxCanvas,
|
|
11
11
|
} from '../server/index.js';
|
|
12
12
|
import type { PmxAxSource } from '../server/ax-state.js';
|
|
13
|
-
import {
|
|
14
|
-
HttpOperationInvoker,
|
|
15
|
-
LocalOperationInvoker,
|
|
16
|
-
type OperationInvoker,
|
|
17
|
-
} from '../server/operations/index.js';
|
|
13
|
+
import { HttpOperationInvoker, LocalOperationInvoker, type OperationInvoker } from '../server/operations/index.js';
|
|
18
14
|
|
|
19
15
|
// openMcpApp / addDiagram / buildWebArtifact / refreshWebpageNode / addHtmlNode /
|
|
20
16
|
// addHtmlPrimitive CanvasAccess methods + their type aliases removed with the
|
|
@@ -66,7 +62,10 @@ export interface CanvasAccess {
|
|
|
66
62
|
listWorkItems(): Promise<ListWorkItemsResult>;
|
|
67
63
|
listApprovalGates(): Promise<ListApprovalGatesResult>;
|
|
68
64
|
listReviewAnnotations(): Promise<ListReviewAnnotationsResult>;
|
|
69
|
-
submitAxInteraction(
|
|
65
|
+
submitAxInteraction(
|
|
66
|
+
input: SubmitAxInteractionInput,
|
|
67
|
+
options?: { source?: PmxAxSource },
|
|
68
|
+
): Promise<SubmitAxInteractionResult>;
|
|
70
69
|
getPendingSteering(options?: { consumer?: string; limit?: number }): Promise<GetPendingSteeringResult>;
|
|
71
70
|
listElicitations(): Promise<ListElicitationsResult>;
|
|
72
71
|
listModeRequests(): Promise<ListModeRequestsResult>;
|
|
@@ -120,7 +119,10 @@ class LocalCanvasAccess implements CanvasAccess {
|
|
|
120
119
|
return this.canvas.getAxTimeline(query);
|
|
121
120
|
}
|
|
122
121
|
|
|
123
|
-
async submitAxInteraction(
|
|
122
|
+
async submitAxInteraction(
|
|
123
|
+
input: SubmitAxInteractionInput,
|
|
124
|
+
options?: { source?: PmxAxSource },
|
|
125
|
+
): Promise<SubmitAxInteractionResult> {
|
|
124
126
|
return this.canvas.submitAxInteraction(input, { source: options?.source ?? 'mcp' });
|
|
125
127
|
}
|
|
126
128
|
|
|
@@ -213,9 +215,10 @@ class RemoteCanvasAccess implements CanvasAccess {
|
|
|
213
215
|
}
|
|
214
216
|
}
|
|
215
217
|
if (!response.ok) {
|
|
216
|
-
const error =
|
|
217
|
-
|
|
218
|
-
|
|
218
|
+
const error =
|
|
219
|
+
parsed && typeof parsed === 'object' && 'error' in parsed
|
|
220
|
+
? String((parsed as { error?: unknown }).error)
|
|
221
|
+
: `HTTP ${response.status}`;
|
|
219
222
|
if (path === '/api/canvas/batch' && parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
220
223
|
return parsed as T;
|
|
221
224
|
}
|
|
@@ -232,7 +235,7 @@ class RemoteCanvasAccess implements CanvasAccess {
|
|
|
232
235
|
const response = await fetch(`${this.remoteBaseUrl}/api/canvas/node/${encodeURIComponent(id)}?includeBlobs=true`);
|
|
233
236
|
if (response.status === 404) return undefined;
|
|
234
237
|
const text = await response.text();
|
|
235
|
-
let parsed: unknown
|
|
238
|
+
let parsed: unknown;
|
|
236
239
|
if (text.length > 0) {
|
|
237
240
|
try {
|
|
238
241
|
parsed = JSON.parse(text) as unknown;
|
|
@@ -241,9 +244,10 @@ class RemoteCanvasAccess implements CanvasAccess {
|
|
|
241
244
|
}
|
|
242
245
|
}
|
|
243
246
|
if (!response.ok) {
|
|
244
|
-
const error =
|
|
245
|
-
|
|
246
|
-
|
|
247
|
+
const error =
|
|
248
|
+
parsed && typeof parsed === 'object' && 'error' in parsed
|
|
249
|
+
? String((parsed as { error?: unknown }).error)
|
|
250
|
+
: `HTTP ${response.status}`;
|
|
247
251
|
throw new Error(error);
|
|
248
252
|
}
|
|
249
253
|
return parsed as CanvasNodeState;
|
|
@@ -269,7 +273,10 @@ class RemoteCanvasAccess implements CanvasAccess {
|
|
|
269
273
|
return await this.requestJson<GetAxTimelineResult>('GET', `/api/canvas/ax/timeline${qs}`);
|
|
270
274
|
}
|
|
271
275
|
|
|
272
|
-
async submitAxInteraction(
|
|
276
|
+
async submitAxInteraction(
|
|
277
|
+
input: SubmitAxInteractionInput,
|
|
278
|
+
options?: { source?: PmxAxSource },
|
|
279
|
+
): Promise<SubmitAxInteractionResult> {
|
|
273
280
|
// The interaction endpoint returns its structured outcome (ok/code/error) in
|
|
274
281
|
// the body for both accepted and rejected interactions, so read the body
|
|
275
282
|
// regardless of HTTP status rather than throwing on a denial.
|
|
@@ -324,12 +331,18 @@ class RemoteCanvasAccess implements CanvasAccess {
|
|
|
324
331
|
}
|
|
325
332
|
|
|
326
333
|
async listApprovalGates(): Promise<ListApprovalGatesResult> {
|
|
327
|
-
const response = await this.requestJson<{ approvalGates?: ListApprovalGatesResult }>(
|
|
334
|
+
const response = await this.requestJson<{ approvalGates?: ListApprovalGatesResult }>(
|
|
335
|
+
'GET',
|
|
336
|
+
'/api/canvas/ax/approval',
|
|
337
|
+
);
|
|
328
338
|
return response.approvalGates ?? [];
|
|
329
339
|
}
|
|
330
340
|
|
|
331
341
|
async listReviewAnnotations(): Promise<ListReviewAnnotationsResult> {
|
|
332
|
-
const response = await this.requestJson<{ reviewAnnotations?: ListReviewAnnotationsResult }>(
|
|
342
|
+
const response = await this.requestJson<{ reviewAnnotations?: ListReviewAnnotationsResult }>(
|
|
343
|
+
'GET',
|
|
344
|
+
'/api/canvas/ax/review',
|
|
345
|
+
);
|
|
333
346
|
return response.reviewAnnotations ?? [];
|
|
334
347
|
}
|
|
335
348
|
|
|
@@ -400,7 +413,7 @@ async function readHealth(baseUrl: string): Promise<HealthResponse | null> {
|
|
|
400
413
|
try {
|
|
401
414
|
const response = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(400) });
|
|
402
415
|
if (!response.ok) return null;
|
|
403
|
-
return await response.json() as HealthResponse;
|
|
416
|
+
return (await response.json()) as HealthResponse;
|
|
404
417
|
} catch {
|
|
405
418
|
return null;
|
|
406
419
|
}
|
|
@@ -444,7 +457,9 @@ export function shouldAttachToExistingDaemon(
|
|
|
444
457
|
occupant: { ok?: boolean; workspace?: unknown } | null,
|
|
445
458
|
allowSplit: boolean,
|
|
446
459
|
): boolean {
|
|
447
|
-
return
|
|
460
|
+
return (
|
|
461
|
+
!allowSplit && occupant?.ok === true && typeof occupant.workspace === 'string' && occupant.workspace.length > 0
|
|
462
|
+
);
|
|
448
463
|
}
|
|
449
464
|
|
|
450
465
|
/**
|
|
@@ -550,8 +565,7 @@ export async function createCanvasAccess(): Promise<CanvasAccess> {
|
|
|
550
565
|
const boundPort = canvas.port;
|
|
551
566
|
if (boundPort !== port) {
|
|
552
567
|
const occupant = await readHealth(occupantBaseUrl);
|
|
553
|
-
const occupantWorkspace =
|
|
554
|
-
typeof occupant?.workspace === 'string' ? ` (serving ${occupant.workspace})` : '';
|
|
568
|
+
const occupantWorkspace = typeof occupant?.workspace === 'string' ? ` (serving ${occupant.workspace})` : '';
|
|
555
569
|
process.stderr.write(
|
|
556
570
|
`[pmx-canvas] preferred port ${port} was in use${occupantWorkspace}; ` +
|
|
557
571
|
`started this canvas on port ${boundPort} instead. To share one canvas, run the daemon ` +
|
package/src/mcp/server.ts
CHANGED
|
@@ -197,12 +197,19 @@ function compactNodePayload(node: Awaited<ReturnType<CanvasAccess['getNode']>>):
|
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
function compactLayoutPayload(
|
|
200
|
+
function compactLayoutPayload(
|
|
201
|
+
layout: Awaited<ReturnType<CanvasAccess['getLayout']>>,
|
|
202
|
+
pinnedIds: string[],
|
|
203
|
+
): Record<string, unknown> {
|
|
201
204
|
return {
|
|
202
205
|
summary: buildSummaryFromLayout(layout, pinnedIds),
|
|
203
206
|
viewport: layout.viewport,
|
|
204
|
-
annotations: (layout.annotations ?? []).map((annotation) =>
|
|
205
|
-
|
|
207
|
+
annotations: (layout.annotations ?? []).map((annotation) =>
|
|
208
|
+
summarizeCanvasAnnotationForContext(annotation, layout.nodes),
|
|
209
|
+
),
|
|
210
|
+
nodes: layout.nodes
|
|
211
|
+
.map((node) => compactNodePayload(node))
|
|
212
|
+
.filter((node): node is Record<string, unknown> => node !== null),
|
|
206
213
|
edges: layout.edges.map((edge) => ({
|
|
207
214
|
id: edge.id,
|
|
208
215
|
from: edge.from,
|
|
@@ -218,11 +225,16 @@ function compactLayoutPayload(layout: Awaited<ReturnType<CanvasAccess['getLayout
|
|
|
218
225
|
function agentSafeFullLayoutPayload(layout: Awaited<ReturnType<CanvasAccess['getLayout']>>): Record<string, unknown> {
|
|
219
226
|
return {
|
|
220
227
|
...serializeCanvasLayoutForAgent(layout),
|
|
221
|
-
annotations: (layout.annotations ?? []).map((annotation) =>
|
|
228
|
+
annotations: (layout.annotations ?? []).map((annotation) =>
|
|
229
|
+
summarizeCanvasAnnotationForContext(annotation, layout.nodes),
|
|
230
|
+
),
|
|
222
231
|
};
|
|
223
232
|
}
|
|
224
233
|
|
|
225
|
-
function buildSummaryFromLayout(
|
|
234
|
+
function buildSummaryFromLayout(
|
|
235
|
+
layout: Awaited<ReturnType<CanvasAccess['getLayout']>>,
|
|
236
|
+
pinnedIds: string[],
|
|
237
|
+
): Record<string, unknown> {
|
|
226
238
|
const pinned = new Set(pinnedIds);
|
|
227
239
|
const nodesByType: Record<string, number> = {};
|
|
228
240
|
const pinnedTitles: string[] = [];
|
|
@@ -235,7 +247,9 @@ function buildSummaryFromLayout(layout: Awaited<ReturnType<CanvasAccess['getLayo
|
|
|
235
247
|
totalNodes: layout.nodes.length,
|
|
236
248
|
totalEdges: layout.edges.length,
|
|
237
249
|
totalAnnotations: (layout.annotations ?? []).length,
|
|
238
|
-
annotations: (layout.annotations ?? []).map((annotation) =>
|
|
250
|
+
annotations: (layout.annotations ?? []).map((annotation) =>
|
|
251
|
+
summarizeCanvasAnnotationForContext(annotation, layout.nodes),
|
|
252
|
+
),
|
|
239
253
|
nodesByType,
|
|
240
254
|
pinnedCount: pinned.size,
|
|
241
255
|
pinnedTitles,
|
|
@@ -276,12 +290,18 @@ export async function startMcpServer(): Promise<void> {
|
|
|
276
290
|
'canvas_ax_interaction',
|
|
277
291
|
'Submit a node-originated AX interaction: a capability-gated, validated event from an eligible node that maps onto an AX operation (work item, evidence, approval, review, focus, steering, event). Returns { ok: false, code } if the node type/metadata does not allow the interaction type or the payload is invalid.',
|
|
278
292
|
{
|
|
279
|
-
type: z
|
|
293
|
+
type: z
|
|
294
|
+
.enum(AX_INTERACTION_TYPES)
|
|
295
|
+
.describe('Interaction type, e.g. ax.work.create, ax.evidence.add, ax.focus.set.'),
|
|
280
296
|
sourceNodeId: z.string().describe('The node emitting the interaction.'),
|
|
281
|
-
payload: z
|
|
297
|
+
payload: z
|
|
298
|
+
.record(z.string(), z.unknown())
|
|
299
|
+
.optional()
|
|
300
|
+
.describe('Type-specific payload, e.g. {"title":"..."} for ax.work.create.'),
|
|
282
301
|
sourceSurface: z.enum(['native-node', 'json-render', 'html-node', 'mcp-app', 'adapter']).optional(),
|
|
283
302
|
correlationId: z.string().optional(),
|
|
284
|
-
source: z
|
|
303
|
+
source: z
|
|
304
|
+
.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system'])
|
|
285
305
|
.optional()
|
|
286
306
|
.describe('Optional host/source label. Defaults to mcp.'),
|
|
287
307
|
},
|
|
@@ -319,29 +339,59 @@ export async function startMcpServer(): Promise<void> {
|
|
|
319
339
|
'canvas_ingest_activity',
|
|
320
340
|
'Ingest a normalized agent activity (a tool/session event your harness forwards) so the board reacts automatically — primitive A, makes AX bidirectional. Always records a timeline event; kind-driven default reactions (overridable per call via `reactions`): failure/error → work item (blocked) + review finding + evidence (logs); tool-result + outcome:"success" → evidence (tool-result); everything else (tool-start, session-*, command, note) → event only. Set any reaction to false to suppress it, or to an object to override its fields. Returns { event, workItem, evidence, review }.',
|
|
321
341
|
{
|
|
322
|
-
kind: z.enum([
|
|
342
|
+
kind: z.enum([
|
|
343
|
+
'tool-start',
|
|
344
|
+
'tool-result',
|
|
345
|
+
'failure',
|
|
346
|
+
'error',
|
|
347
|
+
'session-start',
|
|
348
|
+
'session-end',
|
|
349
|
+
'command',
|
|
350
|
+
'note',
|
|
351
|
+
]),
|
|
323
352
|
title: z.string(),
|
|
324
353
|
summary: z.string().optional(),
|
|
325
354
|
outcome: z.enum(['success', 'failure']).optional(),
|
|
326
|
-
ref: z
|
|
355
|
+
ref: z
|
|
356
|
+
.string()
|
|
357
|
+
.optional()
|
|
358
|
+
.describe('A file path, URL, or commit the activity refers to (used as the review file anchor for failures).'),
|
|
327
359
|
nodeIds: z.array(z.string()).optional(),
|
|
328
360
|
data: z.record(z.string(), z.unknown()).optional(),
|
|
329
|
-
reactions: z
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
361
|
+
reactions: z
|
|
362
|
+
.object({
|
|
363
|
+
workItem: z
|
|
364
|
+
.union([
|
|
365
|
+
z.literal(false),
|
|
366
|
+
z.object({
|
|
367
|
+
status: z.enum(['todo', 'in-progress', 'blocked', 'done', 'cancelled']).optional(),
|
|
368
|
+
detail: z.string().nullable().optional(),
|
|
369
|
+
}),
|
|
370
|
+
])
|
|
371
|
+
.optional(),
|
|
372
|
+
evidence: z
|
|
373
|
+
.union([
|
|
374
|
+
z.literal(false),
|
|
375
|
+
z.object({
|
|
376
|
+
kind: z.enum(['logs', 'tool-result', 'screenshot', 'file', 'diff', 'test-output']).optional(),
|
|
377
|
+
body: z.string().nullable().optional(),
|
|
378
|
+
}),
|
|
379
|
+
])
|
|
380
|
+
.optional(),
|
|
381
|
+
review: z
|
|
382
|
+
.union([
|
|
383
|
+
z.literal(false),
|
|
384
|
+
z.object({
|
|
385
|
+
severity: z.enum(['info', 'warning', 'error']).optional(),
|
|
386
|
+
kind: z.enum(['comment', 'finding']).optional(),
|
|
387
|
+
anchorType: z.enum(['node', 'file', 'region']).optional(),
|
|
388
|
+
nodeId: z.string().nullable().optional(),
|
|
389
|
+
}),
|
|
390
|
+
])
|
|
391
|
+
.optional(),
|
|
392
|
+
})
|
|
393
|
+
.optional()
|
|
394
|
+
.describe('Override or suppress the kind-driven default reactions.'),
|
|
345
395
|
source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system']).optional(),
|
|
346
396
|
},
|
|
347
397
|
async ({ kind, title, summary, outcome, ref, nodeIds, data, reactions, source }) => {
|
|
@@ -391,7 +441,10 @@ export async function startMcpServer(): Promise<void> {
|
|
|
391
441
|
'canvas_screenshot',
|
|
392
442
|
'Capture a screenshot from the active Bun.WebView automation session. Returns both an MCP image payload and JSON metadata. Requires an active automation session started via canvas_webview with action "start".',
|
|
393
443
|
{
|
|
394
|
-
format: z
|
|
444
|
+
format: z
|
|
445
|
+
.enum(['png', 'jpeg', 'webp'])
|
|
446
|
+
.optional()
|
|
447
|
+
.describe('Screenshot format (default depends on Bun; png recommended)'),
|
|
395
448
|
quality: z.number().optional().describe('Optional quality for lossy formats'),
|
|
396
449
|
},
|
|
397
450
|
async ({ format, quality }) => {
|
|
@@ -407,27 +460,21 @@ export async function startMcpServer(): Promise<void> {
|
|
|
407
460
|
{
|
|
408
461
|
type: 'image',
|
|
409
462
|
data: encodeBase64(bytes),
|
|
410
|
-
mimeType:
|
|
411
|
-
format === 'jpeg'
|
|
412
|
-
? 'image/jpeg'
|
|
413
|
-
: format === 'webp'
|
|
414
|
-
? 'image/webp'
|
|
415
|
-
: 'image/png',
|
|
463
|
+
mimeType: format === 'jpeg' ? 'image/jpeg' : format === 'webp' ? 'image/webp' : 'image/png',
|
|
416
464
|
},
|
|
417
465
|
{
|
|
418
466
|
type: 'text',
|
|
419
|
-
text: JSON.stringify(
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
format === 'jpeg'
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}, null, 2),
|
|
467
|
+
text: JSON.stringify(
|
|
468
|
+
{
|
|
469
|
+
bytes: bytes.byteLength,
|
|
470
|
+
backend: status.backend,
|
|
471
|
+
width: status.width,
|
|
472
|
+
height: status.height,
|
|
473
|
+
mimeType: format === 'jpeg' ? 'image/jpeg' : format === 'webp' ? 'image/webp' : 'image/png',
|
|
474
|
+
},
|
|
475
|
+
null,
|
|
476
|
+
2,
|
|
477
|
+
),
|
|
431
478
|
},
|
|
432
479
|
],
|
|
433
480
|
};
|
|
@@ -449,8 +496,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
449
496
|
'schema',
|
|
450
497
|
'canvas://schema',
|
|
451
498
|
{
|
|
452
|
-
description:
|
|
453
|
-
`Machine-readable create schemas, canonical examples, json-render catalog details, and MCP node-type routing from the running PMX Canvas server version. Routing: ${structuredSchemaDescription()}.`,
|
|
499
|
+
description: `Machine-readable create schemas, canonical examples, json-render catalog details, and MCP node-type routing from the running PMX Canvas server version. Routing: ${structuredSchemaDescription()}.`,
|
|
454
500
|
mimeType: 'application/json',
|
|
455
501
|
},
|
|
456
502
|
async () => ({
|
|
@@ -480,20 +526,20 @@ export async function startMcpServer(): Promise<void> {
|
|
|
480
526
|
const layout = await c.getLayout();
|
|
481
527
|
|
|
482
528
|
const pinnedNodes = layout.nodes.filter((n) => pinnedIds.has(n.id));
|
|
483
|
-
const pinnedEdges = layout.edges.filter(
|
|
484
|
-
(e) => pinnedIds.has(e.from) && pinnedIds.has(e.to),
|
|
485
|
-
);
|
|
529
|
+
const pinnedEdges = layout.edges.filter((e) => pinnedIds.has(e.from) && pinnedIds.has(e.to));
|
|
486
530
|
|
|
487
531
|
// Compute neighborhoods: for each pinned node, find nearby unpinned nodes
|
|
488
532
|
const neighborhoods = findNeighborhoods(layout.nodes, pinnedIds);
|
|
489
533
|
|
|
490
534
|
const context = {
|
|
491
535
|
pinnedCount: pinnedNodes.length,
|
|
492
|
-
nodes: pinnedNodes.map((n) =>
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
536
|
+
nodes: pinnedNodes.map((n) =>
|
|
537
|
+
serializeNodeForAgentContext(n, {
|
|
538
|
+
defaultTextLength: 700,
|
|
539
|
+
webpageTextLength: 1600,
|
|
540
|
+
includePosition: true,
|
|
541
|
+
}),
|
|
542
|
+
),
|
|
497
543
|
edges: pinnedEdges.map((e) => ({
|
|
498
544
|
id: e.id,
|
|
499
545
|
from: e.from,
|
|
@@ -547,8 +593,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
547
593
|
'ax-context',
|
|
548
594
|
'canvas://ax-context',
|
|
549
595
|
{
|
|
550
|
-
description:
|
|
551
|
-
'Agent-ready PMX AX context combining pinned context, focus, and surface metadata.',
|
|
596
|
+
description: 'Agent-ready PMX AX context combining pinned context, focus, and surface metadata.',
|
|
552
597
|
mimeType: 'application/json',
|
|
553
598
|
},
|
|
554
599
|
async () => {
|
|
@@ -606,14 +651,18 @@ export async function startMcpServer(): Promise<void> {
|
|
|
606
651
|
{
|
|
607
652
|
uri: 'canvas://ax-work',
|
|
608
653
|
mimeType: 'application/json',
|
|
609
|
-
text: JSON.stringify(
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
654
|
+
text: JSON.stringify(
|
|
655
|
+
{
|
|
656
|
+
workItems,
|
|
657
|
+
approvalGates,
|
|
658
|
+
reviewAnnotations: state.reviewAnnotations,
|
|
659
|
+
elicitations: state.elicitations,
|
|
660
|
+
modeRequests: state.modeRequests,
|
|
661
|
+
policy: state.policy,
|
|
662
|
+
},
|
|
663
|
+
null,
|
|
664
|
+
2,
|
|
665
|
+
),
|
|
617
666
|
},
|
|
618
667
|
],
|
|
619
668
|
};
|
|
@@ -634,7 +683,11 @@ export async function startMcpServer(): Promise<void> {
|
|
|
634
683
|
const pendingActivity = buildPendingAxActivity(state);
|
|
635
684
|
return {
|
|
636
685
|
contents: [
|
|
637
|
-
{
|
|
686
|
+
{
|
|
687
|
+
uri: 'canvas://ax-pending-steering',
|
|
688
|
+
mimeType: 'application/json',
|
|
689
|
+
text: JSON.stringify({ pending, pendingActivity }, null, 2),
|
|
690
|
+
},
|
|
638
691
|
],
|
|
639
692
|
};
|
|
640
693
|
},
|
|
@@ -653,7 +706,11 @@ export async function startMcpServer(): Promise<void> {
|
|
|
653
706
|
const timeline = await c.getAxTimeline();
|
|
654
707
|
return {
|
|
655
708
|
contents: [
|
|
656
|
-
{
|
|
709
|
+
{
|
|
710
|
+
uri: 'canvas://ax-delivery',
|
|
711
|
+
mimeType: 'application/json',
|
|
712
|
+
text: JSON.stringify({ steering: timeline.steering }, null, 2),
|
|
713
|
+
},
|
|
657
714
|
],
|
|
658
715
|
};
|
|
659
716
|
},
|
|
@@ -732,13 +789,18 @@ export async function startMcpServer(): Promise<void> {
|
|
|
732
789
|
'grouped together), provides reading order (top-left to bottom-right), and shows ' +
|
|
733
790
|
'neighborhoods around pinned nodes (nearby unpinned nodes the human implicitly associated). ' +
|
|
734
791
|
'This makes "spatial arrangement is communication" real — read this to understand the ' +
|
|
735
|
-
|
|
792
|
+
"human's spatial intent, not just which nodes are pinned.",
|
|
736
793
|
mimeType: 'application/json',
|
|
737
794
|
},
|
|
738
795
|
async () => {
|
|
739
796
|
const c = await ensureCanvas();
|
|
740
797
|
const layout = await c.getLayout();
|
|
741
|
-
const spatial = buildSpatialContext(
|
|
798
|
+
const spatial = buildSpatialContext(
|
|
799
|
+
layout.nodes,
|
|
800
|
+
layout.edges,
|
|
801
|
+
new Set(await c.getPinnedNodeIds()),
|
|
802
|
+
layout.annotations ?? [],
|
|
803
|
+
);
|
|
742
804
|
return {
|
|
743
805
|
contents: [
|
|
744
806
|
{
|
|
@@ -56,11 +56,8 @@ function summarizeWebpageData(data: Record<string, unknown>, maxLength: number):
|
|
|
56
56
|
const status = typeof data.status === 'string' ? data.status : '';
|
|
57
57
|
const statusCode = typeof data.statusCode === 'number' ? data.statusCode : null;
|
|
58
58
|
const error = typeof data.error === 'string' ? data.error : '';
|
|
59
|
-
const content =
|
|
60
|
-
? data.content
|
|
61
|
-
: typeof data.excerpt === 'string'
|
|
62
|
-
? data.excerpt
|
|
63
|
-
: '';
|
|
59
|
+
const content =
|
|
60
|
+
typeof data.content === 'string' ? data.content : typeof data.excerpt === 'string' ? data.excerpt : '';
|
|
64
61
|
|
|
65
62
|
if (url) parts.push(`URL: ${url}`);
|
|
66
63
|
if (pageTitle) parts.push(`Title: ${pageTitle}`);
|
|
@@ -132,9 +129,7 @@ function summarizeWebArtifactData(data: Record<string, unknown>, maxLength: numb
|
|
|
132
129
|
const sourceFiles = Array.isArray(data.sourceFiles)
|
|
133
130
|
? data.sourceFiles.filter((file): file is string => typeof file === 'string')
|
|
134
131
|
: [];
|
|
135
|
-
const deps = Array.isArray(data.deps)
|
|
136
|
-
? data.deps.filter((dep): dep is string => typeof dep === 'string')
|
|
137
|
-
: [];
|
|
132
|
+
const deps = Array.isArray(data.deps) ? data.deps.filter((dep): dep is string => typeof dep === 'string') : [];
|
|
138
133
|
|
|
139
134
|
if (content) parts.push(content);
|
|
140
135
|
if (!content && title) parts.push(`Web artifact: ${title}`);
|
|
@@ -170,7 +165,16 @@ function metadataForNode(node: CanvasNodeState): Record<string, unknown> | undef
|
|
|
170
165
|
switch (node.type) {
|
|
171
166
|
case 'webpage': {
|
|
172
167
|
const metadata: Record<string, unknown> = {};
|
|
173
|
-
for (const key of [
|
|
168
|
+
for (const key of [
|
|
169
|
+
'url',
|
|
170
|
+
'pageTitle',
|
|
171
|
+
'description',
|
|
172
|
+
'imageUrl',
|
|
173
|
+
'fetchedAt',
|
|
174
|
+
'status',
|
|
175
|
+
'statusCode',
|
|
176
|
+
'contentType',
|
|
177
|
+
]) {
|
|
174
178
|
const value = node.data[key];
|
|
175
179
|
if (value !== undefined && value !== null && value !== '') metadata[key] = value;
|
|
176
180
|
}
|
|
@@ -194,7 +198,19 @@ function metadataForNode(node: CanvasNodeState): Record<string, unknown> | undef
|
|
|
194
198
|
}
|
|
195
199
|
case 'html': {
|
|
196
200
|
const metadata: Record<string, unknown> = {};
|
|
197
|
-
for (const key of [
|
|
201
|
+
for (const key of [
|
|
202
|
+
'summary',
|
|
203
|
+
'description',
|
|
204
|
+
'agentSummary',
|
|
205
|
+
'contentSummary',
|
|
206
|
+
'htmlPrimitive',
|
|
207
|
+
'presentation',
|
|
208
|
+
'slideCount',
|
|
209
|
+
'slideTitles',
|
|
210
|
+
'speakerNotes',
|
|
211
|
+
'embeddedNodeIds',
|
|
212
|
+
'embeddedUrls',
|
|
213
|
+
]) {
|
|
198
214
|
const value = node.data[key];
|
|
199
215
|
if (Array.isArray(value)) {
|
|
200
216
|
if (value.length > 0) metadata[key] = value;
|
|
@@ -206,7 +222,22 @@ function metadataForNode(node: CanvasNodeState): Record<string, unknown> | undef
|
|
|
206
222
|
}
|
|
207
223
|
case 'mcp-app': {
|
|
208
224
|
const metadata: Record<string, unknown> = {};
|
|
209
|
-
for (const key of [
|
|
225
|
+
for (const key of [
|
|
226
|
+
'url',
|
|
227
|
+
'path',
|
|
228
|
+
'mode',
|
|
229
|
+
'hostMode',
|
|
230
|
+
'viewerType',
|
|
231
|
+
'serverName',
|
|
232
|
+
'toolName',
|
|
233
|
+
'resourceUri',
|
|
234
|
+
'sessionStatus',
|
|
235
|
+
'projectPath',
|
|
236
|
+
'artifactBytes',
|
|
237
|
+
'sourceFiles',
|
|
238
|
+
'sourceFileCount',
|
|
239
|
+
'deps',
|
|
240
|
+
]) {
|
|
210
241
|
const value = node.data[key];
|
|
211
242
|
if (Array.isArray(value)) {
|
|
212
243
|
if (value.length > 0) metadata[key] = value;
|
|
@@ -221,10 +252,7 @@ function metadataForNode(node: CanvasNodeState): Record<string, unknown> | undef
|
|
|
221
252
|
}
|
|
222
253
|
}
|
|
223
254
|
|
|
224
|
-
export function summarizeNodeForAgentContext(
|
|
225
|
-
node: CanvasNodeState,
|
|
226
|
-
options: AgentContextOptions = {},
|
|
227
|
-
): string {
|
|
255
|
+
export function summarizeNodeForAgentContext(node: CanvasNodeState, options: AgentContextOptions = {}): string {
|
|
228
256
|
const defaultTextLength = options.defaultTextLength ?? DEFAULT_CONTEXT_TEXT_LENGTH;
|
|
229
257
|
const webpageTextLength = options.webpageTextLength ?? DEFAULT_WEBPAGE_CONTEXT_TEXT_LENGTH;
|
|
230
258
|
|
|
@@ -241,9 +269,7 @@ export function summarizeNodeForAgentContext(
|
|
|
241
269
|
if (chartCfg) {
|
|
242
270
|
const chartTitle = (chartCfg.title as string) || 'Untitled chart';
|
|
243
271
|
const chartType = (chartCfg.type as string) || 'unknown';
|
|
244
|
-
const labels = Array.isArray(chartCfg.labels)
|
|
245
|
-
? (chartCfg.labels as string[]).join(', ')
|
|
246
|
-
: '';
|
|
272
|
+
const labels = Array.isArray(chartCfg.labels) ? (chartCfg.labels as string[]).join(', ') : '';
|
|
247
273
|
return truncateContextText(`Chart: ${chartTitle} (${chartType}). Labels: ${labels}`, defaultTextLength);
|
|
248
274
|
}
|
|
249
275
|
return summarizeMcpAppData(node.data, defaultTextLength);
|
|
@@ -263,14 +289,17 @@ export function summarizeNodeForAgentContext(
|
|
|
263
289
|
if (typeof node.data.htmlPrimitive === 'string') {
|
|
264
290
|
return summarizeHtmlPrimitiveData(node.data, defaultTextLength);
|
|
265
291
|
}
|
|
266
|
-
return stringifyContextValue(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
292
|
+
return stringifyContextValue(
|
|
293
|
+
{
|
|
294
|
+
title: node.data.title,
|
|
295
|
+
description: node.data.description,
|
|
296
|
+
summary: node.data.summary,
|
|
297
|
+
contentSummary: node.data.contentSummary,
|
|
298
|
+
embeddedNodeIds: node.data.embeddedNodeIds,
|
|
299
|
+
embeddedUrls: node.data.embeddedUrls,
|
|
300
|
+
},
|
|
301
|
+
defaultTextLength,
|
|
302
|
+
);
|
|
274
303
|
}
|
|
275
304
|
case 'prompt':
|
|
276
305
|
case 'response': {
|
|
@@ -279,11 +308,12 @@ export function summarizeNodeForAgentContext(
|
|
|
279
308
|
}
|
|
280
309
|
case 'file': {
|
|
281
310
|
const path = typeof node.data.path === 'string' ? node.data.path : '';
|
|
282
|
-
const fileContent =
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
311
|
+
const fileContent =
|
|
312
|
+
typeof node.data.fileContent === 'string'
|
|
313
|
+
? node.data.fileContent
|
|
314
|
+
: typeof node.data.content === 'string'
|
|
315
|
+
? node.data.content
|
|
316
|
+
: '';
|
|
287
317
|
const prefix = path ? `Path: ${path}\n\n` : '';
|
|
288
318
|
const remaining = Math.max(0, defaultTextLength - prefix.length);
|
|
289
319
|
return `${prefix}${truncateContextText(fileContent, remaining)}`.trim();
|
|
@@ -309,13 +339,10 @@ export function serializeNodeForAgentContext(
|
|
|
309
339
|
};
|
|
310
340
|
}
|
|
311
341
|
|
|
312
|
-
export function buildAgentContextPreamble(
|
|
313
|
-
nodes: CanvasNodeState[],
|
|
314
|
-
options: AgentContextOptions = {},
|
|
315
|
-
): string {
|
|
342
|
+
export function buildAgentContextPreamble(nodes: CanvasNodeState[], options: AgentContextOptions = {}): string {
|
|
316
343
|
const sections = nodes
|
|
317
344
|
.map((node) => {
|
|
318
|
-
const title =
|
|
345
|
+
const title = typeof node.data.title === 'string' && node.data.title ? node.data.title : node.id;
|
|
319
346
|
const content = summarizeNodeForAgentContext(node, options);
|
|
320
347
|
if (!content) return '';
|
|
321
348
|
const kind = getCanvasNodeKind(node);
|
package/src/server/ax-context.ts
CHANGED
|
@@ -53,11 +53,13 @@ export function buildCanvasAxSurfaceSnapshot(): PmxAxSurfaceSnapshot {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
function serializeNodes(nodes: CanvasNodeState[]) {
|
|
56
|
-
return nodes.map((node) =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
return nodes.map((node) =>
|
|
57
|
+
serializeNodeForAgentContext(node, {
|
|
58
|
+
defaultTextLength: 700,
|
|
59
|
+
webpageTextLength: 1600,
|
|
60
|
+
includePosition: true,
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
export function buildCanvasAxPinnedContext(): PmxAxPinnedContext {
|