pmx-canvas 0.1.26 → 0.1.27
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/.github/extensions/pmx-canvas/extension.mjs +191 -0
- package/CHANGELOG.md +74 -0
- package/Readme.md +74 -27
- package/dist/canvas/index.js +82 -82
- package/dist/json-render/index.css +1 -1
- package/dist/json-render/index.js +944 -164
- package/dist/types/json-render/catalog.d.ts +195 -20
- package/dist/types/json-render/charts/components.d.ts +7 -0
- package/dist/types/json-render/charts/definitions.d.ts +13 -1
- package/dist/types/json-render/charts/tufte-components.d.ts +65 -0
- package/dist/types/json-render/charts/tufte-definitions.d.ts +164 -0
- package/dist/types/json-render/directives.d.ts +23 -0
- package/dist/types/json-render/renderer/index.d.ts +1 -0
- package/dist/types/json-render/server.d.ts +32 -1
- package/dist/types/mcp/canvas-access.d.ts +62 -0
- package/dist/types/server/ax-state.d.ts +170 -0
- package/dist/types/server/canvas-db.d.ts +17 -1
- package/dist/types/server/canvas-operations.d.ts +45 -0
- package/dist/types/server/canvas-schema.d.ts +5 -1
- package/dist/types/server/canvas-state.d.ts +95 -4
- package/dist/types/server/index.d.ts +114 -2
- package/dist/types/server/mutation-history.d.ts +1 -1
- package/docs/cli.md +42 -0
- package/docs/http-api.md +64 -0
- package/docs/mcp.md +23 -5
- package/docs/node-types.md +1 -1
- package/docs/screenshots/codex-app.png +0 -0
- package/docs/screenshots/github-copilot-app.png +0 -0
- package/docs/sdk.md +19 -1
- package/package.json +10 -7
- package/skills/control-session-orchestrator/SKILL.md +359 -0
- package/skills/control-session-orchestrator/evals/evals.json +75 -0
- package/skills/data-analysis/SKILL.md +6 -0
- package/skills/pmx-canvas/SKILL.md +50 -4
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +6 -0
- package/skills/tufte-viz/SKILL.md +157 -0
- package/skills/tufte-viz/references/analytical-design.md +217 -0
- package/skills/tufte-viz/references/tufte-principles.md +147 -0
- package/src/cli/agent.ts +280 -2
- package/src/cli/index.ts +2 -1
- package/src/client/nodes/ExtAppFrame.tsx +23 -1
- package/src/client/nodes/McpAppNode.tsx +6 -2
- package/src/json-render/catalog.ts +22 -1
- package/src/json-render/charts/components.tsx +97 -10
- package/src/json-render/charts/definitions.ts +19 -2
- package/src/json-render/charts/extra-components.tsx +5 -4
- package/src/json-render/charts/tufte-components.tsx +383 -0
- package/src/json-render/charts/tufte-definitions.ts +128 -0
- package/src/json-render/directives.ts +29 -0
- package/src/json-render/renderer/index.css +101 -0
- package/src/json-render/renderer/index.tsx +33 -0
- package/src/json-render/server.ts +257 -5
- package/src/mcp/canvas-access.ts +261 -0
- package/src/mcp/server.ts +496 -7
- package/src/server/ax-context.ts +8 -3
- package/src/server/ax-state.ts +447 -0
- package/src/server/canvas-db.ts +184 -1
- package/src/server/canvas-operations.ts +107 -0
- package/src/server/canvas-schema.ts +26 -3
- package/src/server/canvas-state.ts +349 -2
- package/src/server/index.ts +234 -2
- package/src/server/mutation-history.ts +6 -0
- package/src/server/server.ts +419 -2
package/src/mcp/server.ts
CHANGED
|
@@ -100,7 +100,7 @@ function sleep(ms: number): Promise<void> {
|
|
|
100
100
|
return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
function sendCanvasResourceNotifications(type: 'nodes' | 'pins' | 'ax' = 'nodes'): void {
|
|
103
|
+
function sendCanvasResourceNotifications(type: 'nodes' | 'pins' | 'ax' | 'ax-timeline' = 'nodes'): void {
|
|
104
104
|
const server = resourceNotificationServer;
|
|
105
105
|
if (!server) return;
|
|
106
106
|
try {
|
|
@@ -110,6 +110,12 @@ function sendCanvasResourceNotifications(type: 'nodes' | 'pins' | 'ax' = 'nodes'
|
|
|
110
110
|
if (type === 'pins' || type === 'ax') {
|
|
111
111
|
server.server.sendResourceUpdated({ uri: 'canvas://ax' });
|
|
112
112
|
server.server.sendResourceUpdated({ uri: 'canvas://ax-context' });
|
|
113
|
+
server.server.sendResourceUpdated({ uri: 'canvas://ax-timeline' });
|
|
114
|
+
server.server.sendResourceUpdated({ uri: 'canvas://ax-work' });
|
|
115
|
+
}
|
|
116
|
+
if (type === 'ax-timeline') {
|
|
117
|
+
server.server.sendResourceUpdated({ uri: 'canvas://ax-timeline' });
|
|
118
|
+
server.server.sendResourceUpdated({ uri: 'canvas://ax-context' });
|
|
113
119
|
}
|
|
114
120
|
server.server.sendResourceUpdated({ uri: 'canvas://layout' });
|
|
115
121
|
server.server.sendResourceUpdated({ uri: 'canvas://summary' });
|
|
@@ -126,7 +132,13 @@ function handleRemoteSseFrame(frame: string): void {
|
|
|
126
132
|
const event = eventLine?.slice('event: '.length).trim() ?? '';
|
|
127
133
|
if (!event || event === 'connected' || event === 'ping') return;
|
|
128
134
|
sendCanvasResourceNotifications(
|
|
129
|
-
event === 'context-pins-changed'
|
|
135
|
+
event === 'context-pins-changed'
|
|
136
|
+
? 'pins'
|
|
137
|
+
: event === 'ax-state-changed'
|
|
138
|
+
? 'ax'
|
|
139
|
+
: event === 'ax-event-created'
|
|
140
|
+
? 'ax-timeline'
|
|
141
|
+
: 'nodes',
|
|
130
142
|
);
|
|
131
143
|
}
|
|
132
144
|
|
|
@@ -415,7 +427,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
415
427
|
'canvas_add_html_node',
|
|
416
428
|
'Add a normal html node: a self-contained HTML document (with optional inline <script> and CDN <script src="...">) rendered inside a sandboxed iframe (sandbox="allow-scripts"). This is the default HTML surface for reports, widgets, and bespoke visualizations. Presentation mode is opt-in: only pass presentation:true when the user explicitly asks for a deck/fullscreen presentation, or use canvas_add_html_primitive with kind="presentation". The iframe inherits live canvas theme tokens via injected CSS custom properties (both --c-* and common --color-* aliases) so authored HTML using var(--color-text-secondary), var(--color-bg), etc. renders cohesively. No same-origin access; no top-navigation; no forms. For declarative-only views with zero JS, prefer canvas_add_json_render_node. For React + shadcn + routing or multi-component apps, use canvas_build_web_artifact.',
|
|
417
429
|
{
|
|
418
|
-
html: z.string().describe('HTML document or fragment. Full <html>...</html> documents are passed through with theme styles injected into <head>; bare fragments are wrapped in a minimal document. Inline <script> and remote CDN <script src="..."> are allowed.'),
|
|
430
|
+
html: z.string().describe('HTML document or fragment. Full <html>...</html> documents are passed through with theme styles injected into <head>; bare fragments are wrapped in a minimal document. Inline <script> and remote CDN <script src="..."> are allowed. If this is a bare path to an existing local .html/.htm file, the file contents are read and used as the HTML.'),
|
|
419
431
|
title: z.string().optional().describe('Node title shown in the canvas titlebar.'),
|
|
420
432
|
summary: z.string().optional().describe('Agent-readable semantic summary for this HTML node. If omitted, PMX derives one from visible HTML text.'),
|
|
421
433
|
agentSummary: z.string().optional().describe('Explicit agent-readable summary. Alias for summary with higher priority when both are provided.'),
|
|
@@ -625,7 +637,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
625
637
|
yKey: z.string().optional().describe('Y-axis key for line/bar graphs'),
|
|
626
638
|
zKey: z.string().optional().describe('Optional bubble-size key for scatter charts'),
|
|
627
639
|
nameKey: z.string().optional().describe('Slice name key for pie graphs'),
|
|
628
|
-
valueKey: z.string().optional().describe('
|
|
640
|
+
valueKey: z.string().optional().describe('Value key for pie slices, sparkline, dot-plot, and the bullet measure'),
|
|
629
641
|
axisKey: z.string().optional().describe('Category key for radar charts'),
|
|
630
642
|
metrics: z.array(z.string()).optional().describe('Series keys to plot as radar polygons'),
|
|
631
643
|
series: z.array(z.string()).optional().describe('Series keys for stacked-bar segments'),
|
|
@@ -633,8 +645,23 @@ export async function startMcpServer(): Promise<void> {
|
|
|
633
645
|
lineKey: z.string().optional().describe('Line series key for composed charts'),
|
|
634
646
|
aggregate: z.enum(['sum', 'count', 'avg']).optional().describe('Optional aggregation for repeated keys'),
|
|
635
647
|
color: z.string().optional().describe('Optional graph color'),
|
|
648
|
+
colorBy: z.enum(['series', 'category', 'value', 'none']).optional().describe("Bar charts only: how bars are colored (default 'series')"),
|
|
649
|
+
highlight: z.union([z.number(), z.enum(['max', 'min'])]).nullable().optional().describe("Bar charts only, colorBy='series': which bar gets the accent"),
|
|
636
650
|
barColor: z.string().optional().describe('Optional bar color for composed charts'),
|
|
637
651
|
lineColor: z.string().optional().describe('Optional line color for composed charts'),
|
|
652
|
+
labelKey: z.string().optional().describe('Category label key for dot-plot / bullet / slopegraph rows'),
|
|
653
|
+
targetKey: z.string().optional().describe('Per-row target value key for bullet charts'),
|
|
654
|
+
rangesKey: z.string().optional().describe('Per-row qualitative band thresholds key (number[]) for bullet charts'),
|
|
655
|
+
beforeKey: z.string().optional().describe('Left-column value key for slopegraph'),
|
|
656
|
+
afterKey: z.string().optional().describe('Right-column value key for slopegraph'),
|
|
657
|
+
beforeLabel: z.string().optional().describe('Header label for the slopegraph left column'),
|
|
658
|
+
afterLabel: z.string().optional().describe('Header label for the slopegraph right column'),
|
|
659
|
+
sort: z.enum(['asc', 'desc', 'none']).optional().describe('Row sort order for dot-plot (defaults to desc)'),
|
|
660
|
+
fill: z.boolean().optional().describe('Sparkline: draw a light area fill under the line'),
|
|
661
|
+
showEndDot: z.boolean().optional().describe('Sparkline: draw a dot at the last point (default true)'),
|
|
662
|
+
showMinMax: z.boolean().optional().describe('Sparkline: mark the min and max points'),
|
|
663
|
+
showValue: z.boolean().optional().describe('Sparkline: print the last value inline'),
|
|
664
|
+
colorByDirection: z.boolean().optional().describe('Slopegraph: accent rising lines and mute falling ones (default off)'),
|
|
638
665
|
height: z.number().optional().describe('Optional graph content height'),
|
|
639
666
|
},
|
|
640
667
|
async (input) => {
|
|
@@ -671,8 +698,23 @@ export async function startMcpServer(): Promise<void> {
|
|
|
671
698
|
...(typeof input.lineKey === 'string' ? { lineKey: input.lineKey } : {}),
|
|
672
699
|
...(typeof input.aggregate === 'string' ? { aggregate: input.aggregate } : {}),
|
|
673
700
|
...(typeof input.color === 'string' ? { color: input.color } : {}),
|
|
701
|
+
...(typeof input.colorBy === 'string' ? { colorBy: input.colorBy } : {}),
|
|
702
|
+
...(input.highlight !== undefined ? { highlight: input.highlight } : {}),
|
|
674
703
|
...(typeof input.barColor === 'string' ? { barColor: input.barColor } : {}),
|
|
675
704
|
...(typeof input.lineColor === 'string' ? { lineColor: input.lineColor } : {}),
|
|
705
|
+
...(typeof input.labelKey === 'string' ? { labelKey: input.labelKey } : {}),
|
|
706
|
+
...(typeof input.targetKey === 'string' ? { targetKey: input.targetKey } : {}),
|
|
707
|
+
...(typeof input.rangesKey === 'string' ? { rangesKey: input.rangesKey } : {}),
|
|
708
|
+
...(typeof input.beforeKey === 'string' ? { beforeKey: input.beforeKey } : {}),
|
|
709
|
+
...(typeof input.afterKey === 'string' ? { afterKey: input.afterKey } : {}),
|
|
710
|
+
...(typeof input.beforeLabel === 'string' ? { beforeLabel: input.beforeLabel } : {}),
|
|
711
|
+
...(typeof input.afterLabel === 'string' ? { afterLabel: input.afterLabel } : {}),
|
|
712
|
+
...(typeof input.sort === 'string' ? { sort: input.sort } : {}),
|
|
713
|
+
...(typeof input.fill === 'boolean' ? { fill: input.fill } : {}),
|
|
714
|
+
...(typeof input.showEndDot === 'boolean' ? { showEndDot: input.showEndDot } : {}),
|
|
715
|
+
...(typeof input.showMinMax === 'boolean' ? { showMinMax: input.showMinMax } : {}),
|
|
716
|
+
...(typeof input.showValue === 'boolean' ? { showValue: input.showValue } : {}),
|
|
717
|
+
...(typeof input.colorByDirection === 'boolean' ? { colorByDirection: input.colorByDirection } : {}),
|
|
676
718
|
...(typeof input.height === 'number' ? { height: input.height } : {}),
|
|
677
719
|
},
|
|
678
720
|
});
|
|
@@ -831,19 +873,74 @@ export async function startMcpServer(): Promise<void> {
|
|
|
831
873
|
},
|
|
832
874
|
);
|
|
833
875
|
|
|
876
|
+
// ── canvas_stream_json_render_node ────────────────────────
|
|
877
|
+
server.tool(
|
|
878
|
+
'canvas_stream_json_render_node',
|
|
879
|
+
'Progressively build a json-render node by streaming SpecStream patches, so a panel fills in live as you generate it. Omit nodeId on the first call to create a new streaming node (returns its id); pass that same nodeId on later calls to append more patches; set done=true on the final call. Each call updates the live node. Patches are JSON-Patch operations, e.g. {"op":"add","path":"/elements/card","value":{"type":"Card","props":{"title":"Live"},"children":[]}}, {"op":"replace","path":"/root","value":"card"}, {"op":"add","path":"/elements/card/children/-","value":"row1"}. Build the spec incrementally: set /root, add container elements, then append children. The server accumulates the spec (it is the source of truth); partial specs render what they can.',
|
|
880
|
+
{
|
|
881
|
+
nodeId: z.string().optional().describe('Existing streaming node id to append to; omit to create a new streaming node'),
|
|
882
|
+
title: z.string().optional().describe('Title when creating a new streaming node'),
|
|
883
|
+
patches: z
|
|
884
|
+
.array(z.union([z.string(), z.record(z.string(), z.unknown())]))
|
|
885
|
+
.optional()
|
|
886
|
+
.describe('SpecStream patches to apply this call: JSON-Patch objects ({op,path,value}) or raw JSONL patch lines'),
|
|
887
|
+
done: z.boolean().optional().describe('Set true on the final call to mark the stream complete'),
|
|
888
|
+
x: z.number().optional().describe('Optional X position (new node)'),
|
|
889
|
+
y: z.number().optional().describe('Optional Y position (new node)'),
|
|
890
|
+
width: z.number().optional().describe('Optional node width (new node)'),
|
|
891
|
+
nodeHeight: z.number().optional().describe('Optional node height (new node)'),
|
|
892
|
+
strictSize: z.boolean().optional().describe('Keep explicit node size fixed and scroll overflowing content (new node)'),
|
|
893
|
+
},
|
|
894
|
+
async (input) => {
|
|
895
|
+
const c = await ensureCanvas();
|
|
896
|
+
try {
|
|
897
|
+
const result = await c.streamJsonRenderNode({
|
|
898
|
+
...(typeof input.nodeId === 'string' ? { nodeId: input.nodeId } : {}),
|
|
899
|
+
...(typeof input.title === 'string' ? { title: input.title } : {}),
|
|
900
|
+
...(Array.isArray(input.patches) ? { patches: input.patches } : {}),
|
|
901
|
+
...(input.done === true ? { done: true } : {}),
|
|
902
|
+
...(typeof input.x === 'number' ? { x: input.x } : {}),
|
|
903
|
+
...(typeof input.y === 'number' ? { y: input.y } : {}),
|
|
904
|
+
...(typeof input.width === 'number' ? { width: input.width } : {}),
|
|
905
|
+
...(typeof input.nodeHeight === 'number' ? { height: input.nodeHeight } : {}),
|
|
906
|
+
...(input.strictSize === true ? { strictSize: true } : {}),
|
|
907
|
+
});
|
|
908
|
+
return {
|
|
909
|
+
content: [{
|
|
910
|
+
type: 'text',
|
|
911
|
+
text: JSON.stringify({
|
|
912
|
+
...(await createdNodePayload(c, result.id)),
|
|
913
|
+
url: result.url,
|
|
914
|
+
applied: result.applied,
|
|
915
|
+
skipped: result.skipped,
|
|
916
|
+
specVersion: result.specVersion,
|
|
917
|
+
elementCount: result.elementCount,
|
|
918
|
+
streamStatus: result.streamStatus,
|
|
919
|
+
}, null, 2),
|
|
920
|
+
}],
|
|
921
|
+
};
|
|
922
|
+
} catch (error) {
|
|
923
|
+
return {
|
|
924
|
+
content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
|
|
925
|
+
isError: true,
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
},
|
|
929
|
+
);
|
|
930
|
+
|
|
834
931
|
// ── canvas_add_graph_node ─────────────────────────────────
|
|
835
932
|
server.tool(
|
|
836
933
|
'canvas_add_graph_node',
|
|
837
|
-
'Create a native graph node backed by the json-render chart catalog. Supports line, bar, pie, area, scatter, radar, stacked-bar,
|
|
934
|
+
'Create a native graph node backed by the json-render chart catalog. Supports line, bar, pie, area, scatter, radar, stacked-bar, composed (bar+line), sparkline, dot-plot (Cleveland), bullet (Few KPI vs target), and slopegraph (paired before/after) graphs rendered directly inside PMX Canvas.',
|
|
838
935
|
{
|
|
839
936
|
title: z.string().optional().describe('Optional node title'),
|
|
840
|
-
graphType: z.string().describe('Graph type: line, bar, pie, area, scatter, radar, stacked-bar (or "stack"), composed (or "combo")'),
|
|
937
|
+
graphType: z.string().describe('Graph type: line, bar, pie, area, scatter, radar, stacked-bar (or "stack"), composed (or "combo"), sparkline, dot-plot (or "dot"), bullet, slopegraph (or "slope")'),
|
|
841
938
|
data: z.array(z.record(z.string(), z.unknown())).describe('Array of chart data objects'),
|
|
842
939
|
xKey: z.string().optional().describe('X-axis key (line/bar/area/scatter/stacked/composed)'),
|
|
843
940
|
yKey: z.string().optional().describe('Y-axis key (line/bar/area/scatter); falls back to barKey for composed'),
|
|
844
941
|
zKey: z.string().optional().describe('Optional bubble-size key for scatter charts'),
|
|
845
942
|
nameKey: z.string().optional().describe('Name key for pie graphs'),
|
|
846
|
-
valueKey: z.string().optional().describe('Value key for pie
|
|
943
|
+
valueKey: z.string().optional().describe('Value key for pie slices, sparkline, dot-plot, and the bullet measure'),
|
|
847
944
|
axisKey: z.string().optional().describe('Category key for radar charts'),
|
|
848
945
|
metrics: z.array(z.string()).optional().describe('Series keys to plot as radar polygons (defaults to non-axis numeric columns)'),
|
|
849
946
|
series: z.array(z.string()).optional().describe('Series keys for stacked-bar segments (defaults to non-x numeric columns)'),
|
|
@@ -851,8 +948,30 @@ export async function startMcpServer(): Promise<void> {
|
|
|
851
948
|
lineKey: z.string().optional().describe('Line series key for composed charts'),
|
|
852
949
|
aggregate: z.enum(['sum', 'count', 'avg']).optional().describe('Optional aggregation for repeated x-axis values (line/bar/area/stacked)'),
|
|
853
950
|
color: z.string().optional().describe('Optional series color (line/bar/area/scatter)'),
|
|
951
|
+
colorBy: z
|
|
952
|
+
.enum(['series', 'category', 'value', 'none'])
|
|
953
|
+
.optional()
|
|
954
|
+
.describe("Bar charts only: how bars are colored. 'series' (default) = single accent with one highlighted bar; 'category' = rotate palette per bar; 'value' = shade by magnitude; 'none' = flat. Prefer 'series' — color should encode data, not decorate."),
|
|
955
|
+
highlight: z
|
|
956
|
+
.union([z.number(), z.enum(['max', 'min'])])
|
|
957
|
+
.nullable()
|
|
958
|
+
.optional()
|
|
959
|
+
.describe("Bar charts only, for colorBy='series': which bar gets the accent — 'max' (default), 'min', a 0-based index, or null for no emphasis."),
|
|
854
960
|
barColor: z.string().optional().describe('Optional bar color for composed charts'),
|
|
855
961
|
lineColor: z.string().optional().describe('Optional line color for composed charts'),
|
|
962
|
+
labelKey: z.string().optional().describe('Category label key for dot-plot / bullet / slopegraph rows'),
|
|
963
|
+
targetKey: z.string().optional().describe('Per-row target value key for bullet charts'),
|
|
964
|
+
rangesKey: z.string().optional().describe('Per-row qualitative band thresholds key (number[]) for bullet charts'),
|
|
965
|
+
beforeKey: z.string().optional().describe('Left-column value key for slopegraph'),
|
|
966
|
+
afterKey: z.string().optional().describe('Right-column value key for slopegraph'),
|
|
967
|
+
beforeLabel: z.string().optional().describe('Header label for the slopegraph left column'),
|
|
968
|
+
afterLabel: z.string().optional().describe('Header label for the slopegraph right column'),
|
|
969
|
+
sort: z.enum(['asc', 'desc', 'none']).optional().describe('Row sort order for dot-plot (defaults to desc)'),
|
|
970
|
+
fill: z.boolean().optional().describe('Sparkline: draw a light area fill under the line'),
|
|
971
|
+
showEndDot: z.boolean().optional().describe('Sparkline: draw a dot at the last point (default true)'),
|
|
972
|
+
showMinMax: z.boolean().optional().describe('Sparkline: mark the min and max points'),
|
|
973
|
+
showValue: z.boolean().optional().describe('Sparkline: print the last value inline'),
|
|
974
|
+
colorByDirection: z.boolean().optional().describe('Slopegraph: accent rising lines and mute falling ones (default off — lines use one neutral ink)'),
|
|
856
975
|
height: z.number().optional().describe('Optional chart content height'),
|
|
857
976
|
showLegend: z.boolean().optional().describe('Show chart legend when supported; pass false for compact node layouts'),
|
|
858
977
|
showLabels: z.boolean().optional().describe('Show direct labels when supported, such as pie slice labels (defaults to true)'),
|
|
@@ -881,8 +1000,23 @@ export async function startMcpServer(): Promise<void> {
|
|
|
881
1000
|
...(typeof input.lineKey === 'string' ? { lineKey: input.lineKey } : {}),
|
|
882
1001
|
...(typeof input.aggregate === 'string' ? { aggregate: input.aggregate } : {}),
|
|
883
1002
|
...(typeof input.color === 'string' ? { color: input.color } : {}),
|
|
1003
|
+
...(typeof input.colorBy === 'string' ? { colorBy: input.colorBy } : {}),
|
|
1004
|
+
...(input.highlight !== undefined ? { highlight: input.highlight } : {}),
|
|
884
1005
|
...(typeof input.barColor === 'string' ? { barColor: input.barColor } : {}),
|
|
885
1006
|
...(typeof input.lineColor === 'string' ? { lineColor: input.lineColor } : {}),
|
|
1007
|
+
...(typeof input.labelKey === 'string' ? { labelKey: input.labelKey } : {}),
|
|
1008
|
+
...(typeof input.targetKey === 'string' ? { targetKey: input.targetKey } : {}),
|
|
1009
|
+
...(typeof input.rangesKey === 'string' ? { rangesKey: input.rangesKey } : {}),
|
|
1010
|
+
...(typeof input.beforeKey === 'string' ? { beforeKey: input.beforeKey } : {}),
|
|
1011
|
+
...(typeof input.afterKey === 'string' ? { afterKey: input.afterKey } : {}),
|
|
1012
|
+
...(typeof input.beforeLabel === 'string' ? { beforeLabel: input.beforeLabel } : {}),
|
|
1013
|
+
...(typeof input.afterLabel === 'string' ? { afterLabel: input.afterLabel } : {}),
|
|
1014
|
+
...(typeof input.sort === 'string' ? { sort: input.sort } : {}),
|
|
1015
|
+
...(typeof input.fill === 'boolean' ? { fill: input.fill } : {}),
|
|
1016
|
+
...(typeof input.showEndDot === 'boolean' ? { showEndDot: input.showEndDot } : {}),
|
|
1017
|
+
...(typeof input.showMinMax === 'boolean' ? { showMinMax: input.showMinMax } : {}),
|
|
1018
|
+
...(typeof input.showValue === 'boolean' ? { showValue: input.showValue } : {}),
|
|
1019
|
+
...(typeof input.colorByDirection === 'boolean' ? { colorByDirection: input.colorByDirection } : {}),
|
|
886
1020
|
...(typeof input.height === 'number' ? { height: input.height } : {}),
|
|
887
1021
|
...(typeof input.showLegend === 'boolean' ? { showLegend: input.showLegend } : {}),
|
|
888
1022
|
...(typeof input.showLabels === 'boolean' ? { showLabels: input.showLabels } : {}),
|
|
@@ -1148,6 +1282,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
1148
1282
|
async ({ includeContext }) => {
|
|
1149
1283
|
const c = await ensureCanvas();
|
|
1150
1284
|
const state = await c.getAxState();
|
|
1285
|
+
const host = await c.getHostCapability();
|
|
1151
1286
|
const context = includeContext === false ? undefined : await c.getAxContext();
|
|
1152
1287
|
return {
|
|
1153
1288
|
content: [
|
|
@@ -1156,6 +1291,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
1156
1291
|
text: JSON.stringify({
|
|
1157
1292
|
ok: true,
|
|
1158
1293
|
state,
|
|
1294
|
+
host,
|
|
1159
1295
|
...(context ? { context } : {}),
|
|
1160
1296
|
}),
|
|
1161
1297
|
},
|
|
@@ -1187,6 +1323,312 @@ export async function startMcpServer(): Promise<void> {
|
|
|
1187
1323
|
},
|
|
1188
1324
|
);
|
|
1189
1325
|
|
|
1326
|
+
server.tool(
|
|
1327
|
+
'canvas_record_ax_event',
|
|
1328
|
+
'Record a normalized AX timeline event (prompt/assistant-message/tool-start/tool-result/failure/approval/steering). Timeline events persist for diagnostics and continuity but are not restored by snapshots.',
|
|
1329
|
+
{
|
|
1330
|
+
kind: z.enum(['prompt', 'assistant-message', 'tool-start', 'tool-result', 'failure', 'approval', 'steering'])
|
|
1331
|
+
.describe('Normalized event kind.'),
|
|
1332
|
+
summary: z.string().describe('Short human-readable summary of the event.'),
|
|
1333
|
+
detail: z.string().optional().describe('Optional longer detail or payload text.'),
|
|
1334
|
+
nodeIds: z.array(z.string()).optional().describe('Optional node IDs this event relates to.'),
|
|
1335
|
+
data: z.record(z.string(), z.unknown()).optional().describe('Optional structured data payload.'),
|
|
1336
|
+
source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system'])
|
|
1337
|
+
.optional()
|
|
1338
|
+
.describe('Optional host/source label. Defaults to mcp.'),
|
|
1339
|
+
},
|
|
1340
|
+
async ({ kind, summary, detail, nodeIds, data, source }) => {
|
|
1341
|
+
const c = await ensureCanvas();
|
|
1342
|
+
const event = await c.recordAxEvent(
|
|
1343
|
+
{
|
|
1344
|
+
kind,
|
|
1345
|
+
summary,
|
|
1346
|
+
...(typeof detail === 'string' ? { detail } : {}),
|
|
1347
|
+
...(Array.isArray(nodeIds) ? { nodeIds } : {}),
|
|
1348
|
+
...(data ? { data } : {}),
|
|
1349
|
+
},
|
|
1350
|
+
{ source: source ?? 'mcp' },
|
|
1351
|
+
);
|
|
1352
|
+
return {
|
|
1353
|
+
content: [
|
|
1354
|
+
{
|
|
1355
|
+
type: 'text',
|
|
1356
|
+
text: JSON.stringify({ ok: true, event }),
|
|
1357
|
+
},
|
|
1358
|
+
],
|
|
1359
|
+
};
|
|
1360
|
+
},
|
|
1361
|
+
);
|
|
1362
|
+
|
|
1363
|
+
server.tool(
|
|
1364
|
+
'canvas_send_steering',
|
|
1365
|
+
'Record a steering message: a user instruction from the surface to the active agent session. Persisted on the AX timeline and exposed via canvas://ax-timeline.',
|
|
1366
|
+
{
|
|
1367
|
+
message: z.string().describe('The steering instruction to deliver to the active agent session.'),
|
|
1368
|
+
source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system'])
|
|
1369
|
+
.optional()
|
|
1370
|
+
.describe('Optional host/source label. Defaults to mcp.'),
|
|
1371
|
+
},
|
|
1372
|
+
async ({ message, source }) => {
|
|
1373
|
+
const c = await ensureCanvas();
|
|
1374
|
+
const steering = await c.sendSteering(message, { source: source ?? 'mcp' });
|
|
1375
|
+
return {
|
|
1376
|
+
content: [
|
|
1377
|
+
{
|
|
1378
|
+
type: 'text',
|
|
1379
|
+
text: JSON.stringify({ ok: true, steering }),
|
|
1380
|
+
},
|
|
1381
|
+
],
|
|
1382
|
+
};
|
|
1383
|
+
},
|
|
1384
|
+
);
|
|
1385
|
+
|
|
1386
|
+
server.tool(
|
|
1387
|
+
'canvas_get_ax_timeline',
|
|
1388
|
+
'Read the bounded AX timeline: recent agent-events, evidence, and steering messages plus counts. Use this for diagnostics and session continuity.',
|
|
1389
|
+
{
|
|
1390
|
+
limit: z.number().optional().describe('Max rows per timeline table (default 50, max 200).'),
|
|
1391
|
+
},
|
|
1392
|
+
async ({ limit }) => {
|
|
1393
|
+
const c = await ensureCanvas();
|
|
1394
|
+
const timeline = await c.getAxTimeline(
|
|
1395
|
+
typeof limit === 'number' && limit > 0 ? { limit } : undefined,
|
|
1396
|
+
);
|
|
1397
|
+
return {
|
|
1398
|
+
content: [
|
|
1399
|
+
{
|
|
1400
|
+
type: 'text',
|
|
1401
|
+
text: JSON.stringify({ ok: true, ...timeline }),
|
|
1402
|
+
},
|
|
1403
|
+
],
|
|
1404
|
+
};
|
|
1405
|
+
},
|
|
1406
|
+
);
|
|
1407
|
+
|
|
1408
|
+
server.tool(
|
|
1409
|
+
'canvas_add_work_item',
|
|
1410
|
+
'Add a canvas-bound AX work item: a visible task/plan/status tied to nodes and agent work. Work items participate in snapshots and are exposed via canvas://ax-work.',
|
|
1411
|
+
{
|
|
1412
|
+
title: z.string().describe('Short title of the work item.'),
|
|
1413
|
+
status: z.enum(['todo', 'in-progress', 'blocked', 'done', 'cancelled'])
|
|
1414
|
+
.optional()
|
|
1415
|
+
.describe('Work item status. Defaults to todo.'),
|
|
1416
|
+
detail: z.string().optional().describe('Optional longer description.'),
|
|
1417
|
+
nodeIds: z.array(z.string()).optional().describe('Optional node IDs this work item is tied to.'),
|
|
1418
|
+
source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system'])
|
|
1419
|
+
.optional()
|
|
1420
|
+
.describe('Optional host/source label. Defaults to mcp.'),
|
|
1421
|
+
},
|
|
1422
|
+
async ({ title, status, detail, nodeIds, source }) => {
|
|
1423
|
+
const c = await ensureCanvas();
|
|
1424
|
+
const workItem = await c.addWorkItem(
|
|
1425
|
+
{
|
|
1426
|
+
title,
|
|
1427
|
+
...(status ? { status } : {}),
|
|
1428
|
+
...(typeof detail === 'string' ? { detail } : {}),
|
|
1429
|
+
...(Array.isArray(nodeIds) ? { nodeIds } : {}),
|
|
1430
|
+
},
|
|
1431
|
+
{ source: source ?? 'mcp' },
|
|
1432
|
+
);
|
|
1433
|
+
return {
|
|
1434
|
+
content: [{ type: 'text', text: JSON.stringify({ ok: true, workItem }) }],
|
|
1435
|
+
};
|
|
1436
|
+
},
|
|
1437
|
+
);
|
|
1438
|
+
|
|
1439
|
+
server.tool(
|
|
1440
|
+
'canvas_update_work_item',
|
|
1441
|
+
'Update a canvas-bound AX work item by ID (title/status/detail/nodeIds). Returns null if the work item does not exist.',
|
|
1442
|
+
{
|
|
1443
|
+
id: z.string().describe('Work item ID to update.'),
|
|
1444
|
+
title: z.string().optional().describe('New title.'),
|
|
1445
|
+
status: z.enum(['todo', 'in-progress', 'blocked', 'done', 'cancelled'])
|
|
1446
|
+
.optional()
|
|
1447
|
+
.describe('New status.'),
|
|
1448
|
+
detail: z.string().optional().describe('New detail text.'),
|
|
1449
|
+
nodeIds: z.array(z.string()).optional().describe('Replacement node IDs.'),
|
|
1450
|
+
source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system'])
|
|
1451
|
+
.optional()
|
|
1452
|
+
.describe('Optional host/source label. Defaults to mcp.'),
|
|
1453
|
+
},
|
|
1454
|
+
async ({ id, title, status, detail, nodeIds, source }) => {
|
|
1455
|
+
const c = await ensureCanvas();
|
|
1456
|
+
const workItem = await c.updateWorkItem(
|
|
1457
|
+
id,
|
|
1458
|
+
{
|
|
1459
|
+
...(typeof title === 'string' ? { title } : {}),
|
|
1460
|
+
...(status ? { status } : {}),
|
|
1461
|
+
...(typeof detail === 'string' ? { detail } : {}),
|
|
1462
|
+
...(Array.isArray(nodeIds) ? { nodeIds } : {}),
|
|
1463
|
+
},
|
|
1464
|
+
{ source: source ?? 'mcp' },
|
|
1465
|
+
);
|
|
1466
|
+
return {
|
|
1467
|
+
content: [{ type: 'text', text: JSON.stringify({ ok: workItem !== null, workItem }) }],
|
|
1468
|
+
};
|
|
1469
|
+
},
|
|
1470
|
+
);
|
|
1471
|
+
|
|
1472
|
+
server.tool(
|
|
1473
|
+
'canvas_request_approval',
|
|
1474
|
+
'Request human approval before a high-impact AX action: creates a pending approval gate tied to nodes. Canvas-bound and snapshotted; exposed via canvas://ax-work.',
|
|
1475
|
+
{
|
|
1476
|
+
title: z.string().describe('Short title of what needs approval.'),
|
|
1477
|
+
detail: z.string().optional().describe('Optional explanation of the action and its impact.'),
|
|
1478
|
+
action: z.string().optional().describe('Optional machine-readable action identifier the approval gates.'),
|
|
1479
|
+
nodeIds: z.array(z.string()).optional().describe('Optional node IDs this approval relates to.'),
|
|
1480
|
+
source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system'])
|
|
1481
|
+
.optional()
|
|
1482
|
+
.describe('Optional host/source label. Defaults to mcp.'),
|
|
1483
|
+
},
|
|
1484
|
+
async ({ title, detail, action, nodeIds, source }) => {
|
|
1485
|
+
const c = await ensureCanvas();
|
|
1486
|
+
const approvalGate = await c.requestApproval(
|
|
1487
|
+
{
|
|
1488
|
+
title,
|
|
1489
|
+
...(typeof detail === 'string' ? { detail } : {}),
|
|
1490
|
+
...(typeof action === 'string' ? { action } : {}),
|
|
1491
|
+
...(Array.isArray(nodeIds) ? { nodeIds } : {}),
|
|
1492
|
+
},
|
|
1493
|
+
{ source: source ?? 'mcp' },
|
|
1494
|
+
);
|
|
1495
|
+
return {
|
|
1496
|
+
content: [{ type: 'text', text: JSON.stringify({ ok: true, approvalGate }) }],
|
|
1497
|
+
};
|
|
1498
|
+
},
|
|
1499
|
+
);
|
|
1500
|
+
|
|
1501
|
+
server.tool(
|
|
1502
|
+
'canvas_resolve_approval',
|
|
1503
|
+
'Resolve a pending approval gate by ID with approved or rejected. Returns null if the gate does not exist or is already resolved.',
|
|
1504
|
+
{
|
|
1505
|
+
id: z.string().describe('Approval gate ID to resolve.'),
|
|
1506
|
+
decision: z.enum(['approved', 'rejected']).describe('Approval decision.'),
|
|
1507
|
+
resolution: z.string().optional().describe('Optional human-readable resolution note.'),
|
|
1508
|
+
source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system'])
|
|
1509
|
+
.optional()
|
|
1510
|
+
.describe('Optional host/source label. Defaults to mcp.'),
|
|
1511
|
+
},
|
|
1512
|
+
async ({ id, decision, resolution, source }) => {
|
|
1513
|
+
const c = await ensureCanvas();
|
|
1514
|
+
const approvalGate = await c.resolveApproval(id, decision, {
|
|
1515
|
+
...(typeof resolution === 'string' ? { resolution } : {}),
|
|
1516
|
+
source: source ?? 'mcp',
|
|
1517
|
+
});
|
|
1518
|
+
return {
|
|
1519
|
+
content: [{ type: 'text', text: JSON.stringify({ ok: approvalGate !== null, approvalGate }) }],
|
|
1520
|
+
};
|
|
1521
|
+
},
|
|
1522
|
+
);
|
|
1523
|
+
|
|
1524
|
+
server.tool(
|
|
1525
|
+
'canvas_add_evidence',
|
|
1526
|
+
'Record an AX evidence item (logs/tool-result/screenshot/file/diff/test-output) on the timeline. Evidence persists for diagnostics and continuity but is not restored by snapshots; exposed via canvas://ax-timeline.',
|
|
1527
|
+
{
|
|
1528
|
+
kind: z.enum(['logs', 'tool-result', 'screenshot', 'file', 'diff', 'test-output'])
|
|
1529
|
+
.describe('Evidence kind.'),
|
|
1530
|
+
title: z.string().describe('Short human-readable title for the evidence.'),
|
|
1531
|
+
body: z.string().optional().describe('Optional inline body/content.'),
|
|
1532
|
+
ref: z.string().optional().describe('Optional reference (path, URL, or external locator).'),
|
|
1533
|
+
nodeIds: z.array(z.string()).optional().describe('Optional node IDs this evidence relates to.'),
|
|
1534
|
+
data: z.record(z.string(), z.unknown()).optional().describe('Optional structured data payload.'),
|
|
1535
|
+
source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system'])
|
|
1536
|
+
.optional()
|
|
1537
|
+
.describe('Optional host/source label. Defaults to mcp.'),
|
|
1538
|
+
},
|
|
1539
|
+
async ({ kind, title, body, ref, nodeIds, data, source }) => {
|
|
1540
|
+
const c = await ensureCanvas();
|
|
1541
|
+
const evidence = await c.addEvidence(
|
|
1542
|
+
{
|
|
1543
|
+
kind,
|
|
1544
|
+
title,
|
|
1545
|
+
...(typeof body === 'string' ? { body } : {}),
|
|
1546
|
+
...(typeof ref === 'string' ? { ref } : {}),
|
|
1547
|
+
...(Array.isArray(nodeIds) ? { nodeIds } : {}),
|
|
1548
|
+
...(data ? { data } : {}),
|
|
1549
|
+
},
|
|
1550
|
+
{ source: source ?? 'mcp' },
|
|
1551
|
+
);
|
|
1552
|
+
return {
|
|
1553
|
+
content: [{ type: 'text', text: JSON.stringify({ ok: true, evidence }) }],
|
|
1554
|
+
};
|
|
1555
|
+
},
|
|
1556
|
+
);
|
|
1557
|
+
|
|
1558
|
+
server.tool(
|
|
1559
|
+
'canvas_add_review_annotation',
|
|
1560
|
+
'Add a canvas-bound review annotation: a comment or finding anchored to a node, file, or region. Review annotations participate in snapshots and are exposed via canvas://ax-work.',
|
|
1561
|
+
{
|
|
1562
|
+
body: z.string().describe('Annotation body text.'),
|
|
1563
|
+
kind: z.enum(['comment', 'finding']).optional().describe('Annotation kind. Default comment.'),
|
|
1564
|
+
severity: z.enum(['info', 'warning', 'error']).optional().describe('Severity. Default info.'),
|
|
1565
|
+
anchorType: z.enum(['node', 'file', 'region']).optional().describe('Anchor type. Default node.'),
|
|
1566
|
+
nodeId: z.string().optional().describe('Node ID when anchorType is node.'),
|
|
1567
|
+
file: z.string().optional().describe('File path when anchorType is file.'),
|
|
1568
|
+
region: z.object({
|
|
1569
|
+
line: z.number().optional(),
|
|
1570
|
+
endLine: z.number().optional(),
|
|
1571
|
+
label: z.string().optional(),
|
|
1572
|
+
}).optional().describe('Region descriptor when anchorType is region.'),
|
|
1573
|
+
author: z.string().optional().describe('Optional author label.'),
|
|
1574
|
+
source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system'])
|
|
1575
|
+
.optional()
|
|
1576
|
+
.describe('Optional host/source label. Defaults to mcp.'),
|
|
1577
|
+
},
|
|
1578
|
+
async ({ body, kind, severity, anchorType, nodeId, file, region, author, source }) => {
|
|
1579
|
+
const c = await ensureCanvas();
|
|
1580
|
+
const reviewAnnotation = await c.addReviewAnnotation(
|
|
1581
|
+
{
|
|
1582
|
+
body,
|
|
1583
|
+
...(kind ? { kind } : {}),
|
|
1584
|
+
...(severity ? { severity } : {}),
|
|
1585
|
+
...(anchorType ? { anchorType } : {}),
|
|
1586
|
+
...(typeof nodeId === 'string' ? { nodeId } : {}),
|
|
1587
|
+
...(typeof file === 'string' ? { file } : {}),
|
|
1588
|
+
...(region ? { region } : {}),
|
|
1589
|
+
...(typeof author === 'string' ? { author } : {}),
|
|
1590
|
+
},
|
|
1591
|
+
{ source: source ?? 'mcp' },
|
|
1592
|
+
);
|
|
1593
|
+
if (!reviewAnnotation) {
|
|
1594
|
+
return {
|
|
1595
|
+
content: [{ type: 'text', text: JSON.stringify({ ok: false, error: 'node-anchored review annotation requires a nodeId that exists on the canvas.' }) }],
|
|
1596
|
+
isError: true,
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
return {
|
|
1600
|
+
content: [{ type: 'text', text: JSON.stringify({ ok: true, reviewAnnotation }) }],
|
|
1601
|
+
};
|
|
1602
|
+
},
|
|
1603
|
+
);
|
|
1604
|
+
|
|
1605
|
+
server.tool(
|
|
1606
|
+
'canvas_report_host_capability',
|
|
1607
|
+
'Report host/session capability from an adapter: what the host can do (canvas/hooks/tools/sessionMessaging/permissions/files/uiPrompts). Stored for diagnostics; core does not depend on a host.',
|
|
1608
|
+
{
|
|
1609
|
+
host: z.string().optional().describe('Host identifier (e.g. copilot, codex).'),
|
|
1610
|
+
canvas: z.boolean().optional(),
|
|
1611
|
+
hooks: z.boolean().optional(),
|
|
1612
|
+
tools: z.boolean().optional(),
|
|
1613
|
+
sessionMessaging: z.boolean().optional(),
|
|
1614
|
+
permissions: z.boolean().optional(),
|
|
1615
|
+
files: z.boolean().optional(),
|
|
1616
|
+
uiPrompts: z.boolean().optional(),
|
|
1617
|
+
raw: z.record(z.string(), z.unknown()).optional().describe('Optional raw capability payload for diagnostics.'),
|
|
1618
|
+
source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system'])
|
|
1619
|
+
.optional()
|
|
1620
|
+
.describe('Optional host/source label. Defaults to mcp.'),
|
|
1621
|
+
},
|
|
1622
|
+
async (input) => {
|
|
1623
|
+
const c = await ensureCanvas();
|
|
1624
|
+
const { source, ...capability } = input;
|
|
1625
|
+
const host = await c.reportHostCapability(capability, { source: source ?? 'mcp' });
|
|
1626
|
+
return {
|
|
1627
|
+
content: [{ type: 'text', text: JSON.stringify({ ok: true, host }) }],
|
|
1628
|
+
};
|
|
1629
|
+
},
|
|
1630
|
+
);
|
|
1631
|
+
|
|
1190
1632
|
server.tool(
|
|
1191
1633
|
'canvas_fit_view',
|
|
1192
1634
|
'Fit the canvas viewport to all nodes or a selected subset. Useful before screenshots and whole-board review.',
|
|
@@ -1610,6 +2052,53 @@ export async function startMcpServer(): Promise<void> {
|
|
|
1610
2052
|
},
|
|
1611
2053
|
);
|
|
1612
2054
|
|
|
2055
|
+
server.resource(
|
|
2056
|
+
'ax-timeline',
|
|
2057
|
+
'canvas://ax-timeline',
|
|
2058
|
+
{
|
|
2059
|
+
description:
|
|
2060
|
+
'Bounded PMX AX timeline: recent agent-events, evidence, and steering messages with counts. Persisted for diagnostics and continuity; not restored by snapshots.',
|
|
2061
|
+
mimeType: 'application/json',
|
|
2062
|
+
},
|
|
2063
|
+
async () => {
|
|
2064
|
+
const c = await ensureCanvas();
|
|
2065
|
+
const timeline = await c.getAxTimeline();
|
|
2066
|
+
return {
|
|
2067
|
+
contents: [
|
|
2068
|
+
{
|
|
2069
|
+
uri: 'canvas://ax-timeline',
|
|
2070
|
+
mimeType: 'application/json',
|
|
2071
|
+
text: JSON.stringify(timeline, null, 2),
|
|
2072
|
+
},
|
|
2073
|
+
],
|
|
2074
|
+
};
|
|
2075
|
+
},
|
|
2076
|
+
);
|
|
2077
|
+
|
|
2078
|
+
server.resource(
|
|
2079
|
+
'ax-work',
|
|
2080
|
+
'canvas://ax-work',
|
|
2081
|
+
{
|
|
2082
|
+
description:
|
|
2083
|
+
'Canvas-bound PMX AX work state: work items, approval gates, and review annotations. Participates in snapshots and restore.',
|
|
2084
|
+
mimeType: 'application/json',
|
|
2085
|
+
},
|
|
2086
|
+
async () => {
|
|
2087
|
+
const c = await ensureCanvas();
|
|
2088
|
+
const [workItems, approvalGates] = await Promise.all([c.listWorkItems(), c.listApprovalGates()]);
|
|
2089
|
+
const state = await c.getAxState();
|
|
2090
|
+
return {
|
|
2091
|
+
contents: [
|
|
2092
|
+
{
|
|
2093
|
+
uri: 'canvas://ax-work',
|
|
2094
|
+
mimeType: 'application/json',
|
|
2095
|
+
text: JSON.stringify({ workItems, approvalGates, reviewAnnotations: state.reviewAnnotations }, null, 2),
|
|
2096
|
+
},
|
|
2097
|
+
],
|
|
2098
|
+
};
|
|
2099
|
+
},
|
|
2100
|
+
);
|
|
2101
|
+
|
|
1613
2102
|
server.resource(
|
|
1614
2103
|
'canvas-layout',
|
|
1615
2104
|
'canvas://layout',
|
package/src/server/ax-context.ts
CHANGED
|
@@ -25,14 +25,19 @@ export function buildCanvasAxPinnedContext(): PmxAxPinnedContext {
|
|
|
25
25
|
|
|
26
26
|
export function buildCanvasAxContext(): PmxAxContext {
|
|
27
27
|
const layout = canvasState.getLayout();
|
|
28
|
-
const
|
|
29
|
-
const focusNodes = focus.nodeIds
|
|
28
|
+
const ax = canvasState.getAxState();
|
|
29
|
+
const focusNodes = ax.focus.nodeIds
|
|
30
30
|
.map((id) => canvasState.getNode(id))
|
|
31
31
|
.filter((node): node is CanvasNodeState => node !== undefined);
|
|
32
32
|
return buildAxContext({
|
|
33
33
|
layout,
|
|
34
34
|
pinned: buildCanvasAxPinnedContext(),
|
|
35
|
-
focus,
|
|
35
|
+
focus: ax.focus,
|
|
36
36
|
focusNodes: serializeNodes(focusNodes),
|
|
37
|
+
workItems: ax.workItems,
|
|
38
|
+
approvalGates: ax.approvalGates,
|
|
39
|
+
reviewAnnotations: ax.reviewAnnotations,
|
|
40
|
+
timeline: canvasState.getAxTimelineSummary(),
|
|
41
|
+
host: canvasState.getHostCapability(),
|
|
37
42
|
});
|
|
38
43
|
}
|