pmx-canvas 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/dist/canvas/index.js +2 -2
- 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/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/docs/http-api.md +28 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
- package/docs/tech-debt-assessment-2026-07.md +2 -2
- package/package.json +5 -2
- package/skills/pmx-canvas/SKILL.md +3 -1
- package/skills/pmx-canvas/references/full-reference.md +10 -3
- package/src/cli/agent.ts +1861 -1548
- package/src/cli/daemon.ts +460 -0
- package/src/cli/index.ts +63 -326
- 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 +60 -39
- 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 +177 -120
- 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 +39 -14
- 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 +108 -1027
- 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
|
@@ -19,7 +19,16 @@ import { tufteChartComponents } from '../charts/tufte-components';
|
|
|
19
19
|
import { pmxCanvasDirectives } from '../directives';
|
|
20
20
|
import { JsonRenderDevtools } from '@json-render/devtools-react';
|
|
21
21
|
|
|
22
|
-
type BadgeVariant =
|
|
22
|
+
type BadgeVariant =
|
|
23
|
+
| 'default'
|
|
24
|
+
| 'secondary'
|
|
25
|
+
| 'destructive'
|
|
26
|
+
| 'outline'
|
|
27
|
+
| 'success'
|
|
28
|
+
| 'info'
|
|
29
|
+
| 'warning'
|
|
30
|
+
| 'error'
|
|
31
|
+
| 'danger';
|
|
23
32
|
type BadgeProps = {
|
|
24
33
|
text: string;
|
|
25
34
|
variant?: BadgeVariant | null;
|
|
@@ -30,11 +39,7 @@ function Badge({ props }: { props: BadgeProps }) {
|
|
|
30
39
|
const variant = props.variant;
|
|
31
40
|
const resolvedVariant = variant ?? 'default';
|
|
32
41
|
return (
|
|
33
|
-
<span
|
|
34
|
-
data-slot="badge"
|
|
35
|
-
data-variant={resolvedVariant}
|
|
36
|
-
className={`pmx-badge pmx-badge--${resolvedVariant}`}
|
|
37
|
-
>
|
|
42
|
+
<span data-slot="badge" data-variant={resolvedVariant} className={`pmx-badge pmx-badge--${resolvedVariant}`}>
|
|
38
43
|
{props.text}
|
|
39
44
|
</span>
|
|
40
45
|
);
|
|
@@ -111,9 +116,17 @@ function AxStateSync() {
|
|
|
111
116
|
// validates + submits through the capability-gated endpoint). Convention-based
|
|
112
117
|
// opt-in: spec authors name the action handler after the AX interaction type.
|
|
113
118
|
const AX_INTERACTION_HANDLER_NAMES = [
|
|
114
|
-
'ax.event.record',
|
|
115
|
-
'ax.
|
|
116
|
-
'ax.
|
|
119
|
+
'ax.event.record',
|
|
120
|
+
'ax.steer',
|
|
121
|
+
'ax.work.create',
|
|
122
|
+
'ax.work.update',
|
|
123
|
+
'ax.evidence.add',
|
|
124
|
+
'ax.approval.request',
|
|
125
|
+
'ax.review.add',
|
|
126
|
+
'ax.focus.set',
|
|
127
|
+
'ax.elicitation.request',
|
|
128
|
+
'ax.mode.request',
|
|
129
|
+
'ax.command.invoke',
|
|
117
130
|
] as const;
|
|
118
131
|
|
|
119
132
|
function buildAxHandlers(): Record<string, (params: Record<string, unknown>) => void> {
|
|
@@ -126,12 +139,15 @@ function buildAxHandlers(): Record<string, (params: Record<string, unknown>) =>
|
|
|
126
139
|
// is no JS surface for a Promise-style ack here, so we don't stamp a correlationId.
|
|
127
140
|
for (const type of AX_INTERACTION_HANDLER_NAMES) {
|
|
128
141
|
handlers[type] = (params: Record<string, unknown>) => {
|
|
129
|
-
window.parent.postMessage(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
142
|
+
window.parent.postMessage(
|
|
143
|
+
{
|
|
144
|
+
source: 'pmx-canvas-ax',
|
|
145
|
+
token,
|
|
146
|
+
nodeId,
|
|
147
|
+
interaction: { type, payload: params && typeof params === 'object' ? params : {} },
|
|
148
|
+
},
|
|
149
|
+
'*',
|
|
150
|
+
);
|
|
135
151
|
};
|
|
136
152
|
}
|
|
137
153
|
return handlers;
|
|
@@ -176,16 +192,22 @@ function App() {
|
|
|
176
192
|
|
|
177
193
|
// Seed AX state under a reserved `/ax` key so specs can bind { "$state": "/ax/workItems" }.
|
|
178
194
|
const axState = window.__PMX_CANVAS_AX_STATE__;
|
|
179
|
-
const initialState =
|
|
180
|
-
? { ...(spec.state ?? {}), ax: axState }
|
|
181
|
-
: spec.state ?? undefined;
|
|
195
|
+
const initialState =
|
|
196
|
+
axState !== undefined && axState !== null ? { ...(spec.state ?? {}), ax: axState } : (spec.state ?? undefined);
|
|
182
197
|
|
|
183
198
|
// Standalone "Open as site" tab (#65): fill the browser viewport instead of the
|
|
184
199
|
// in-canvas card height. The chart child flex-grows; useChartFrameHeight measures
|
|
185
200
|
// the full viewport in this mode. Embedded/expanded keep the padded min-height box.
|
|
186
201
|
const isSite = window.__PMX_CANVAS_JSON_RENDER_DISPLAY__ === 'site';
|
|
187
202
|
const containerStyle = isSite
|
|
188
|
-
? {
|
|
203
|
+
? {
|
|
204
|
+
display: 'flex',
|
|
205
|
+
flexDirection: 'column' as const,
|
|
206
|
+
height: '100dvh',
|
|
207
|
+
minHeight: '100dvh',
|
|
208
|
+
padding: 0,
|
|
209
|
+
boxSizing: 'border-box' as const,
|
|
210
|
+
}
|
|
189
211
|
: { minHeight: '100vh', padding: 16, boxSizing: 'border-box' as const };
|
|
190
212
|
return (
|
|
191
213
|
<div style={containerStyle}>
|
|
@@ -199,9 +221,7 @@ function App() {
|
|
|
199
221
|
<div style={isSite ? { flex: 1, minHeight: 0 } : undefined}>
|
|
200
222
|
<Renderer spec={spec} registry={registry} loading={false} />
|
|
201
223
|
</div>
|
|
202
|
-
{window.__PMX_CANVAS_JSON_RENDER_DEVTOOLS__ ?
|
|
203
|
-
<JsonRenderDevtools position="right" />
|
|
204
|
-
) : null}
|
|
224
|
+
{window.__PMX_CANVAS_JSON_RENDER_DEVTOOLS__ ? <JsonRenderDevtools position="right" /> : null}
|
|
205
225
|
</JSONUIProvider>
|
|
206
226
|
</div>
|
|
207
227
|
);
|
|
@@ -34,11 +34,13 @@ export const schema = defineSchema(
|
|
|
34
34
|
builtInActions: [
|
|
35
35
|
{
|
|
36
36
|
name: 'setState',
|
|
37
|
-
description:
|
|
37
|
+
description:
|
|
38
|
+
'Update a value in the state model at the given statePath. Params: { statePath: string, value: any }',
|
|
38
39
|
},
|
|
39
40
|
{
|
|
40
41
|
name: 'pushState',
|
|
41
|
-
description:
|
|
42
|
+
description:
|
|
43
|
+
'Append an item to an array in state. Params: { statePath: string, value: any, clearStatePath?: string }. Value can contain {"$state":"/path"} refs and "$id" for auto IDs.',
|
|
42
44
|
},
|
|
43
45
|
{
|
|
44
46
|
name: 'removeState',
|
|
@@ -46,7 +48,8 @@ export const schema = defineSchema(
|
|
|
46
48
|
},
|
|
47
49
|
{
|
|
48
50
|
name: 'validateForm',
|
|
49
|
-
description:
|
|
51
|
+
description:
|
|
52
|
+
'Validate all registered form fields and write the result to state. Params: { statePath?: string }. Defaults to /formValidation. Result: { valid: boolean, errors: Record<string, string[]> }.',
|
|
50
53
|
},
|
|
51
54
|
],
|
|
52
55
|
defaultRules: [
|
|
@@ -212,9 +212,7 @@ async function ensureJsonRenderBundle(): Promise<void> {
|
|
|
212
212
|
// Avoid live source-vs-dist rebuild checks here because Bun's bundler can stall on
|
|
213
213
|
// the @json-render/shadcn dependency graph during request-time viewer generation.
|
|
214
214
|
const needsBuild =
|
|
215
|
-
!existsSync(jsPath) ||
|
|
216
|
-
!existsSync(cssPath) ||
|
|
217
|
-
process.env.PMX_CANVAS_FORCE_JSON_RENDER_REBUILD === '1';
|
|
215
|
+
!existsSync(jsPath) || !existsSync(cssPath) || process.env.PMX_CANVAS_FORCE_JSON_RENDER_REBUILD === '1';
|
|
218
216
|
|
|
219
217
|
if (needsBuild) {
|
|
220
218
|
await rebuildJsonRenderBundle();
|
|
@@ -273,7 +271,7 @@ function normalizeItemArray(value: unknown): unknown {
|
|
|
273
271
|
if (hasString(item)) return { label: item, value: item };
|
|
274
272
|
const record = asRecord(item);
|
|
275
273
|
const label = hasString(record?.label) ? record.label : hasString(record?.text) ? record.text : null;
|
|
276
|
-
const resolvedValue = hasString(record?.value) ? record.value : label ?? `option-${index + 1}
|
|
274
|
+
const resolvedValue = hasString(record?.value) ? record.value : (label ?? `option-${index + 1}`);
|
|
277
275
|
return {
|
|
278
276
|
label: label ?? resolvedValue,
|
|
279
277
|
value: resolvedValue,
|
|
@@ -283,11 +281,7 @@ function normalizeItemArray(value: unknown): unknown {
|
|
|
283
281
|
|
|
284
282
|
function normalizeStringMatrix(value: unknown): unknown {
|
|
285
283
|
if (!Array.isArray(value)) return value;
|
|
286
|
-
return value.map((row) => (
|
|
287
|
-
Array.isArray(row)
|
|
288
|
-
? row.map((cell) => String(cell ?? ''))
|
|
289
|
-
: [String(row ?? '')]
|
|
290
|
-
));
|
|
284
|
+
return value.map((row) => (Array.isArray(row) ? row.map((cell) => String(cell ?? '')) : [String(row ?? '')]));
|
|
291
285
|
}
|
|
292
286
|
|
|
293
287
|
function normalizeButtonVariant(value: unknown): unknown {
|
|
@@ -347,12 +341,7 @@ function normalizeElementProps(
|
|
|
347
341
|
const props = (stripNullishDeep(rawProps) as Record<string, unknown> | undefined) ?? {};
|
|
348
342
|
|
|
349
343
|
for (const key of COERCIBLE_STRING_PROPS) {
|
|
350
|
-
if (
|
|
351
|
-
key in props &&
|
|
352
|
-
typeof props[key] !== 'string' &&
|
|
353
|
-
props[key] !== undefined &&
|
|
354
|
-
!isDynamicPropValue(props[key])
|
|
355
|
-
) {
|
|
344
|
+
if (key in props && typeof props[key] !== 'string' && props[key] !== undefined && !isDynamicPropValue(props[key])) {
|
|
356
345
|
props[key] = String(props[key]);
|
|
357
346
|
}
|
|
358
347
|
}
|
|
@@ -516,7 +505,9 @@ export function inferJsonRenderNodeTitle(spec: JsonRenderSpec, fallback = 'json-
|
|
|
516
505
|
export function normalizeAndValidateJsonRenderSpec(spec: unknown): JsonRenderSpec {
|
|
517
506
|
const specRecord = asRecord(normalizeJsonRenderInput(spec));
|
|
518
507
|
if (!specRecord || typeof specRecord.root !== 'string' || !asRecord(specRecord.elements)) {
|
|
519
|
-
throw new Error(
|
|
508
|
+
throw new Error(
|
|
509
|
+
'Missing root and elements in spec. Pass a complete {root, elements} document, or a single bare component object with a type field.',
|
|
510
|
+
);
|
|
520
511
|
}
|
|
521
512
|
|
|
522
513
|
// Reject an unrecognized $-keyed expression object in any element prop BEFORE
|
|
@@ -576,10 +567,7 @@ function collectDataKeys(data: Array<Record<string, unknown>>): Set<string> {
|
|
|
576
567
|
* as "value") without an explicit valueKey, instead of failing the data-key
|
|
577
568
|
* check on a reasonable guess.
|
|
578
569
|
*/
|
|
579
|
-
function firstPresentDataKey(
|
|
580
|
-
data: Array<Record<string, unknown>>,
|
|
581
|
-
candidates: string[],
|
|
582
|
-
): string | undefined {
|
|
570
|
+
function firstPresentDataKey(data: Array<Record<string, unknown>>, candidates: string[]): string | undefined {
|
|
583
571
|
const keys = collectDataKeys(data);
|
|
584
572
|
return candidates.find((key) => keys.has(key));
|
|
585
573
|
}
|
|
@@ -653,7 +641,9 @@ function assertGraphDataKeys(
|
|
|
653
641
|
const missing = [...required].filter((key) => !available.has(key));
|
|
654
642
|
if (missing.length === 0) return;
|
|
655
643
|
const availableList = [...available].sort().join(', ') || '(none)';
|
|
656
|
-
throw new Error(
|
|
644
|
+
throw new Error(
|
|
645
|
+
`Graph data key mismatch for ${chartType}: missing ${missing.join(', ')}. Available keys: ${availableList}.`,
|
|
646
|
+
);
|
|
657
647
|
}
|
|
658
648
|
|
|
659
649
|
export function buildGraphSpec(input: GraphNodeInput): JsonRenderSpec {
|
|
@@ -685,11 +675,11 @@ export function buildGraphSpec(input: GraphNodeInput): JsonRenderSpec {
|
|
|
685
675
|
}
|
|
686
676
|
case 'RadarChart': {
|
|
687
677
|
const axisKey = input.axisKey ?? input.xKey ?? 'axis';
|
|
688
|
-
const metrics = input.metrics?.length
|
|
689
|
-
? input.metrics
|
|
690
|
-
: inferKeysFromData(input.data, [axisKey]);
|
|
678
|
+
const metrics = input.metrics?.length ? input.metrics : inferKeysFromData(input.data, [axisKey]);
|
|
691
679
|
if (metrics.length === 0) {
|
|
692
|
-
throw new Error(
|
|
680
|
+
throw new Error(
|
|
681
|
+
'RadarChart requires at least one metric key (provide `metrics` or include numeric columns in `data`).',
|
|
682
|
+
);
|
|
693
683
|
}
|
|
694
684
|
chartProps.axisKey = axisKey;
|
|
695
685
|
chartProps.metrics = metrics;
|
|
@@ -698,11 +688,11 @@ export function buildGraphSpec(input: GraphNodeInput): JsonRenderSpec {
|
|
|
698
688
|
}
|
|
699
689
|
case 'StackedBarChart': {
|
|
700
690
|
const xKey = input.xKey ?? 'label';
|
|
701
|
-
const series = input.series?.length
|
|
702
|
-
? input.series
|
|
703
|
-
: inferKeysFromData(input.data, [xKey]);
|
|
691
|
+
const series = input.series?.length ? input.series : inferKeysFromData(input.data, [xKey]);
|
|
704
692
|
if (series.length === 0) {
|
|
705
|
-
throw new Error(
|
|
693
|
+
throw new Error(
|
|
694
|
+
'StackedBarChart requires at least one series key (provide `series` or include numeric columns in `data`).',
|
|
695
|
+
);
|
|
706
696
|
}
|
|
707
697
|
chartProps.xKey = xKey;
|
|
708
698
|
chartProps.series = series;
|
|
@@ -713,9 +703,10 @@ export function buildGraphSpec(input: GraphNodeInput): JsonRenderSpec {
|
|
|
713
703
|
case 'ComposedChart': {
|
|
714
704
|
chartProps.xKey = input.xKey ?? 'label';
|
|
715
705
|
chartProps.barKey = input.barKey ?? input.yKey ?? 'value';
|
|
716
|
-
chartProps.lineKey =
|
|
717
|
-
|
|
718
|
-
??
|
|
706
|
+
chartProps.lineKey =
|
|
707
|
+
input.lineKey ??
|
|
708
|
+
inferKeysFromData(input.data, [chartProps.xKey as string, chartProps.barKey as string])[0] ??
|
|
709
|
+
'rate';
|
|
719
710
|
chartProps.barColor = input.barColor ?? null;
|
|
720
711
|
chartProps.lineColor = input.lineColor ?? null;
|
|
721
712
|
chartProps.showLegend = input.showLegend !== false;
|
|
@@ -952,7 +943,8 @@ export async function buildJsonRenderViewerHtml(options: {
|
|
|
952
943
|
* node grows to it. Off for strictSize / user-resized nodes (they fill-down). */
|
|
953
944
|
fitContent?: boolean;
|
|
954
945
|
}): Promise<string> {
|
|
955
|
-
const sanitizeAxValue = (v?: string): string =>
|
|
946
|
+
const sanitizeAxValue = (v?: string): string =>
|
|
947
|
+
typeof v === 'string' ? v.replace(/[^A-Za-z0-9_-]/g, '').slice(0, 80) : '';
|
|
956
948
|
try {
|
|
957
949
|
await ensureJsonRenderBundle();
|
|
958
950
|
const dir = bundleDir();
|
|
@@ -967,12 +959,14 @@ export async function buildJsonRenderViewerHtml(options: {
|
|
|
967
959
|
...(options.theme ? [`window.__PMX_CANVAS_JSON_RENDER_THEME__ = ${JSON.stringify(options.theme)};`] : []),
|
|
968
960
|
...(options.display ? [`window.__PMX_CANVAS_JSON_RENDER_DISPLAY__ = ${JSON.stringify(options.display)};`] : []),
|
|
969
961
|
...(options.devtools ? ['window.__PMX_CANVAS_JSON_RENDER_DEVTOOLS__ = true;'] : []),
|
|
970
|
-
...(options.nodeId && options.axToken
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
962
|
+
...(options.nodeId && options.axToken
|
|
963
|
+
? [
|
|
964
|
+
`window.__PMX_CANVAS_JSON_RENDER_NODE_ID__ = ${JSON.stringify(sanitizeAxValue(options.nodeId))};`,
|
|
965
|
+
`window.__PMX_CANVAS_AX_TOKEN__ = ${JSON.stringify(sanitizeAxValue(options.axToken))};`,
|
|
966
|
+
// Read-side AX state: seed for initial render + bound under /ax for specs.
|
|
967
|
+
`window.__PMX_CANVAS_AX_STATE__ = ${JSON.stringify(options.axState ?? null).replace(/</g, '\\u003c')};`,
|
|
968
|
+
]
|
|
969
|
+
: []),
|
|
976
970
|
...(options.fitContent ? ['window.__PMX_CANVAS_FIT_CONTENT__ = true;'] : []),
|
|
977
971
|
jsBundle,
|
|
978
972
|
].join('\n');
|
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 ` +
|