pmx-canvas 0.1.29 → 0.1.30

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +161 -0
  2. package/Readme.md +20 -10
  3. package/dist/canvas/global.css +13 -0
  4. package/dist/canvas/index.js +80 -163
  5. package/dist/canvas/surface-theme.css +142 -0
  6. package/dist/json-render/index.js +103 -103
  7. package/dist/types/client/nodes/HtmlNode.d.ts +0 -7
  8. package/dist/types/client/nodes/ax-node-actions.d.ts +18 -0
  9. package/dist/types/client/nodes/surface-url.d.ts +22 -0
  10. package/dist/types/client/state/attention-bridge.d.ts +3 -0
  11. package/dist/types/client/state/intent-bridge.d.ts +17 -0
  12. package/dist/types/json-render/renderer/index.d.ts +2 -0
  13. package/dist/types/json-render/schema.d.ts +2 -0
  14. package/dist/types/json-render/server.d.ts +2 -0
  15. package/dist/types/mcp/canvas-access.d.ts +47 -0
  16. package/dist/types/server/ax-interaction.d.ts +210 -0
  17. package/dist/types/server/ax-state.d.ts +67 -1
  18. package/dist/types/server/canvas-db.d.ts +4 -0
  19. package/dist/types/server/canvas-serialization.d.ts +2 -0
  20. package/dist/types/server/canvas-state.d.ts +47 -2
  21. package/dist/types/server/html-surface.d.ts +40 -0
  22. package/dist/types/server/index.d.ts +50 -2
  23. package/dist/types/server/mutation-history.d.ts +1 -1
  24. package/dist/types/server/placement.d.ts +1 -1
  25. package/dist/types/shared/surface.d.ts +19 -0
  26. package/docs/cli.md +30 -0
  27. package/docs/http-api.md +55 -0
  28. package/docs/mcp.md +40 -2
  29. package/docs/node-types.md +26 -0
  30. package/docs/plans/plan-004-pmx-ax-primitives.md +623 -394
  31. package/docs/sdk.md +20 -0
  32. package/package.json +2 -2
  33. package/skills/pmx-canvas/SKILL.md +107 -9
  34. package/src/cli/agent.ts +177 -0
  35. package/src/client/canvas/CanvasNode.tsx +8 -4
  36. package/src/client/canvas/ExpandedNodeOverlay.tsx +12 -0
  37. package/src/client/nodes/ContextNode.tsx +17 -0
  38. package/src/client/nodes/ExtAppFrame.tsx +40 -3
  39. package/src/client/nodes/FileNode.tsx +26 -0
  40. package/src/client/nodes/HtmlNode.tsx +60 -188
  41. package/src/client/nodes/McpAppNode.tsx +47 -2
  42. package/src/client/nodes/StatusNode.tsx +20 -0
  43. package/src/client/nodes/ax-node-actions.ts +39 -0
  44. package/src/client/nodes/surface-url.ts +48 -0
  45. package/src/client/state/attention-bridge.ts +5 -0
  46. package/src/client/state/intent-bridge.ts +33 -0
  47. package/src/client/theme/global.css +13 -0
  48. package/src/client/theme/surface-theme.css +142 -0
  49. package/src/json-render/renderer/index.tsx +31 -0
  50. package/src/json-render/schema.ts +4 -0
  51. package/src/json-render/server.ts +13 -0
  52. package/src/mcp/canvas-access.ts +195 -0
  53. package/src/mcp/server.ts +232 -2
  54. package/src/server/ax-context.ts +3 -0
  55. package/src/server/ax-interaction.ts +549 -0
  56. package/src/server/ax-state.ts +188 -2
  57. package/src/server/canvas-db.ts +20 -0
  58. package/src/server/canvas-operations.ts +11 -0
  59. package/src/server/canvas-serialization.ts +9 -0
  60. package/src/server/canvas-state.ts +177 -16
  61. package/src/server/html-surface.ts +170 -0
  62. package/src/server/index.ts +98 -0
  63. package/src/server/mutation-history.ts +5 -0
  64. package/src/server/placement.ts +5 -1
  65. package/src/server/server.ts +305 -0
  66. package/src/shared/surface.ts +38 -0
package/src/mcp/server.ts CHANGED
@@ -25,6 +25,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
25
25
  import { isAbsolute, relative, resolve } from 'node:path';
26
26
  import { z } from 'zod';
27
27
  import { canvasState, describeCanvasSchema, validateStructuredCanvasPayload } from '../server/index.js';
28
+ import { AX_INTERACTION_TYPES } from '../server/ax-interaction.js';
28
29
  import { isHtmlPrimitiveKind } from '../server/html-primitives.js';
29
30
  import type { HtmlPrimitiveKind } from '../server/html-primitives.js';
30
31
  import { createCanvasAccess, refreshCanvasAccess, type CanvasAccess } from './canvas-access.js';
@@ -116,6 +117,8 @@ function sendCanvasResourceNotifications(type: 'nodes' | 'pins' | 'ax' | 'ax-tim
116
117
  if (type === 'ax-timeline') {
117
118
  server.server.sendResourceUpdated({ uri: 'canvas://ax-timeline' });
118
119
  server.server.sendResourceUpdated({ uri: 'canvas://ax-context' });
120
+ server.server.sendResourceUpdated({ uri: 'canvas://ax-pending-steering' });
121
+ server.server.sendResourceUpdated({ uri: 'canvas://ax-delivery' });
119
122
  }
120
123
  server.server.sendResourceUpdated({ uri: 'canvas://layout' });
121
124
  server.server.sendResourceUpdated({ uri: 'canvas://summary' });
@@ -1632,6 +1635,171 @@ export async function startMcpServer(): Promise<void> {
1632
1635
  },
1633
1636
  );
1634
1637
 
1638
+ server.tool(
1639
+ 'canvas_ax_interaction',
1640
+ '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.',
1641
+ {
1642
+ type: z.enum(AX_INTERACTION_TYPES).describe('Interaction type, e.g. ax.work.create, ax.evidence.add, ax.focus.set.'),
1643
+ sourceNodeId: z.string().describe('The node emitting the interaction.'),
1644
+ payload: z.record(z.string(), z.unknown()).optional().describe('Type-specific payload, e.g. {"title":"..."} for ax.work.create.'),
1645
+ sourceSurface: z.enum(['native-node', 'json-render', 'html-node', 'mcp-app', 'adapter']).optional(),
1646
+ correlationId: z.string().optional(),
1647
+ source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system'])
1648
+ .optional()
1649
+ .describe('Optional host/source label. Defaults to mcp.'),
1650
+ },
1651
+ async ({ type, sourceNodeId, payload, sourceSurface, correlationId, source }) => {
1652
+ const c = await ensureCanvas();
1653
+ const result = await c.submitAxInteraction(
1654
+ {
1655
+ type,
1656
+ sourceNodeId,
1657
+ ...(payload ? { payload } : {}),
1658
+ ...(sourceSurface ? { sourceSurface } : {}),
1659
+ ...(correlationId ? { correlationId } : {}),
1660
+ },
1661
+ { source: source ?? 'mcp' },
1662
+ );
1663
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
1664
+ },
1665
+ );
1666
+
1667
+ server.tool(
1668
+ 'canvas_claim_ax_delivery',
1669
+ 'Claim pending PMX AX steering messages for a consumer (adapterless delivery). Returns undelivered steering, excluding messages the consumer itself originated (loop prevention). After acting on a message, call canvas_mark_ax_delivery.',
1670
+ {
1671
+ consumer: z.string().optional().describe('Consumer/source label to exclude from results (e.g. copilot, mcp).'),
1672
+ limit: z.number().optional().describe('Max messages to return.'),
1673
+ },
1674
+ async ({ consumer, limit }) => {
1675
+ const c = await ensureCanvas();
1676
+ const pending = await c.getPendingSteering({
1677
+ ...(consumer ? { consumer } : {}),
1678
+ ...(typeof limit === 'number' ? { limit } : {}),
1679
+ });
1680
+ return { content: [{ type: 'text', text: JSON.stringify({ ok: true, pending }) }] };
1681
+ },
1682
+ );
1683
+
1684
+ server.tool(
1685
+ 'canvas_mark_ax_delivery',
1686
+ 'Mark a PMX AX steering message as delivered so it is not handed out again.',
1687
+ {
1688
+ id: z.string().describe('The steering message id to mark delivered.'),
1689
+ },
1690
+ async ({ id }) => {
1691
+ const c = await ensureCanvas();
1692
+ const delivered = await c.markSteeringDelivered(id);
1693
+ return { content: [{ type: 'text', text: JSON.stringify({ ok: true, delivered }) }] };
1694
+ },
1695
+ );
1696
+
1697
+ server.tool(
1698
+ 'canvas_request_elicitation',
1699
+ 'Request structured human input (an elicitation): a pending question/form tied to nodes. Canvas-bound and snapshotted; exposed via canvas://ax-work. Answer it with canvas_respond_elicitation.',
1700
+ {
1701
+ prompt: z.string().describe('The question or instruction for the human.'),
1702
+ fields: z.array(z.string()).optional().describe('Optional field names to request (a simple structured form).'),
1703
+ nodeIds: z.array(z.string()).optional(),
1704
+ source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system']).optional(),
1705
+ },
1706
+ async ({ prompt, fields, nodeIds, source }) => {
1707
+ const c = await ensureCanvas();
1708
+ const elicitation = await c.requestElicitation(
1709
+ { prompt, ...(fields ? { fields } : {}), ...(Array.isArray(nodeIds) ? { nodeIds } : {}) },
1710
+ { source: source ?? 'mcp' },
1711
+ );
1712
+ return { content: [{ type: 'text', text: JSON.stringify({ ok: true, elicitation }) }] };
1713
+ },
1714
+ );
1715
+
1716
+ server.tool(
1717
+ 'canvas_respond_elicitation',
1718
+ 'Answer a pending elicitation with a structured response.',
1719
+ {
1720
+ id: z.string().describe('The elicitation id.'),
1721
+ response: z.record(z.string(), z.unknown()).describe('The structured answer.'),
1722
+ source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system']).optional(),
1723
+ },
1724
+ async ({ id, response, source }) => {
1725
+ const c = await ensureCanvas();
1726
+ const elicitation = await c.respondElicitation(id, response, { source: source ?? 'mcp' });
1727
+ return { content: [{ type: 'text', text: JSON.stringify({ ok: Boolean(elicitation), elicitation }) }] };
1728
+ },
1729
+ );
1730
+
1731
+ server.tool(
1732
+ 'canvas_request_mode',
1733
+ 'Request a workflow mode transition (plan/execute/autonomous): a pending mode request tied to nodes. Canvas-bound and snapshotted; exposed via canvas://ax-work. Resolve with canvas_resolve_mode.',
1734
+ {
1735
+ mode: z.enum(['plan', 'execute', 'autonomous']).describe('Requested target mode.'),
1736
+ reason: z.string().optional(),
1737
+ nodeIds: z.array(z.string()).optional(),
1738
+ source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system']).optional(),
1739
+ },
1740
+ async ({ mode, reason, nodeIds, source }) => {
1741
+ const c = await ensureCanvas();
1742
+ const modeRequest = await c.requestMode(
1743
+ { mode, ...(typeof reason === 'string' ? { reason } : {}), ...(Array.isArray(nodeIds) ? { nodeIds } : {}) },
1744
+ { source: source ?? 'mcp' },
1745
+ );
1746
+ return { content: [{ type: 'text', text: JSON.stringify({ ok: true, modeRequest }) }] };
1747
+ },
1748
+ );
1749
+
1750
+ server.tool(
1751
+ 'canvas_resolve_mode',
1752
+ 'Resolve a pending mode request (approved or rejected).',
1753
+ {
1754
+ id: z.string(),
1755
+ decision: z.enum(['approved', 'rejected']),
1756
+ resolution: z.string().optional(),
1757
+ source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system']).optional(),
1758
+ },
1759
+ async ({ id, decision, resolution, source }) => {
1760
+ const c = await ensureCanvas();
1761
+ const modeRequest = await c.resolveModeRequest(id, decision, {
1762
+ ...(typeof resolution === 'string' ? { resolution } : {}),
1763
+ source: source ?? 'mcp',
1764
+ });
1765
+ return { content: [{ type: 'text', text: JSON.stringify({ ok: Boolean(modeRequest), modeRequest }) }] };
1766
+ },
1767
+ );
1768
+
1769
+ server.tool(
1770
+ 'canvas_invoke_command',
1771
+ 'Invoke a registry-gated PMX command intent (pmx.plan | pmx.execute | pmx.promote-context | pmx.summarize | pmx.review). Records a timeline event a host/agent can observe — NOT arbitrary execution; unknown names are rejected.',
1772
+ {
1773
+ name: z.string().describe('A command name from the PMX command registry.'),
1774
+ args: z.record(z.string(), z.unknown()).optional(),
1775
+ source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system']).optional(),
1776
+ },
1777
+ async ({ name, args, source }) => {
1778
+ const c = await ensureCanvas();
1779
+ const event = await c.invokeCommand(name, args ?? null, { source: source ?? 'mcp' });
1780
+ return { content: [{ type: 'text', text: JSON.stringify({ ok: Boolean(event), event }) }] };
1781
+ },
1782
+ );
1783
+
1784
+ server.tool(
1785
+ 'canvas_set_ax_policy',
1786
+ 'Set the declarative AX policy (allowed/excluded/approval-required tools; prompt mode/append). PMX stores it and exposes it via canvas://ax-context; host adapters READ and enforce it. Merges with the existing policy.',
1787
+ {
1788
+ tools: z.object({
1789
+ allowed: z.array(z.string()).optional(),
1790
+ excluded: z.array(z.string()).optional(),
1791
+ approvalRequired: z.array(z.string()).optional(),
1792
+ }).optional(),
1793
+ prompt: z.object({ systemAppend: z.string().optional(), mode: z.string().optional() }).optional(),
1794
+ source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system']).optional(),
1795
+ },
1796
+ async ({ tools, prompt, source }) => {
1797
+ const c = await ensureCanvas();
1798
+ const policy = await c.setPolicy({ ...(tools ? { tools } : {}), ...(prompt ? { prompt } : {}) }, { source: source ?? 'mcp' });
1799
+ return { content: [{ type: 'text', text: JSON.stringify({ ok: true, policy }) }] };
1800
+ },
1801
+ );
1802
+
1635
1803
  server.tool(
1636
1804
  'canvas_fit_view',
1637
1805
  'Fit the canvas viewport to all nodes or a selected subset. Useful before screenshots and whole-board review.',
@@ -2095,7 +2263,69 @@ export async function startMcpServer(): Promise<void> {
2095
2263
  {
2096
2264
  uri: 'canvas://ax-work',
2097
2265
  mimeType: 'application/json',
2098
- text: JSON.stringify({ workItems, approvalGates, reviewAnnotations: state.reviewAnnotations }, null, 2),
2266
+ text: JSON.stringify({
2267
+ workItems,
2268
+ approvalGates,
2269
+ reviewAnnotations: state.reviewAnnotations,
2270
+ elicitations: state.elicitations,
2271
+ modeRequests: state.modeRequests,
2272
+ policy: state.policy,
2273
+ }, null, 2),
2274
+ },
2275
+ ],
2276
+ };
2277
+ },
2278
+ );
2279
+
2280
+ server.resource(
2281
+ 'ax-pending-steering',
2282
+ 'canvas://ax-pending-steering',
2283
+ {
2284
+ description:
2285
+ 'Undelivered PMX AX steering messages an MCP client can claim and act on without a host-native adapter. After delivering an instruction to your agent, mark it via canvas_mark_ax_delivery so it is not handed out again.',
2286
+ mimeType: 'application/json',
2287
+ },
2288
+ async () => {
2289
+ const c = await ensureCanvas();
2290
+ const pending = await c.getPendingSteering();
2291
+ return {
2292
+ contents: [
2293
+ { uri: 'canvas://ax-pending-steering', mimeType: 'application/json', text: JSON.stringify({ pending }, null, 2) },
2294
+ ],
2295
+ };
2296
+ },
2297
+ );
2298
+
2299
+ server.resource(
2300
+ 'ax-delivery',
2301
+ 'canvas://ax-delivery',
2302
+ {
2303
+ description:
2304
+ 'PMX AX steering delivery state: recent steering messages with their delivered flag, for delivery diagnostics.',
2305
+ mimeType: 'application/json',
2306
+ },
2307
+ async () => {
2308
+ const c = await ensureCanvas();
2309
+ const timeline = await c.getAxTimeline();
2310
+ return {
2311
+ contents: [
2312
+ { uri: 'canvas://ax-delivery', mimeType: 'application/json', text: JSON.stringify({ steering: timeline.steering }, null, 2) },
2313
+ ],
2314
+ };
2315
+ },
2316
+ );
2317
+
2318
+ server.prompt(
2319
+ 'pmx-current-context',
2320
+ 'Inject the current PMX Canvas AX context (pins, focus, work items, approvals, review, timeline) so an MCP-aware client can ground its next action without a host-native adapter.',
2321
+ async () => {
2322
+ const c = await ensureCanvas();
2323
+ const context = await c.getAxContext();
2324
+ return {
2325
+ messages: [
2326
+ {
2327
+ role: 'user',
2328
+ content: { type: 'text', text: `Current PMX Canvas context:\n\n${JSON.stringify(context, null, 2)}` },
2099
2329
  },
2100
2330
  ],
2101
2331
  };
@@ -2335,7 +2565,7 @@ export async function startMcpServer(): Promise<void> {
2335
2565
 
2336
2566
  server.tool(
2337
2567
  'canvas_batch',
2338
- 'Run a non-atomic batch of canvas operations with optional assigned references. Use assign to name a result, then reference it later as "$name" for the created node id or "$name.id" for a specific result field. On failure, earlier successful operations remain applied and the response includes ok:false, failedIndex, error, results, and refs. Supports node.add, node.update, graph.add, edge.add, group.create, group.add, group.remove, pin.set/add/remove, snapshot.save, and arrange.',
2568
+ 'Run a non-atomic batch of canvas operations with optional assigned references. Use assign to name a result, then reference it later as "$name" for the created node id or "$name.id" for a specific result field. On failure, earlier successful operations remain applied and the response includes ok:false, failedIndex, error, results, and refs. Supports node.add, node.update, node.remove, graph.add, edge.add, group.create, group.add, group.remove, pin.set/add/remove, snapshot.save, and arrange.',
2339
2569
  {
2340
2570
  operations: z.array(z.object({
2341
2571
  op: z.string().describe('Operation name, e.g. "node.add" or "edge.add"'),
@@ -37,6 +37,9 @@ export function buildCanvasAxContext(): PmxAxContext {
37
37
  workItems: ax.workItems,
38
38
  approvalGates: ax.approvalGates,
39
39
  reviewAnnotations: ax.reviewAnnotations,
40
+ elicitations: ax.elicitations,
41
+ modeRequests: ax.modeRequests,
42
+ policy: ax.policy,
40
43
  timeline: canvasState.getAxTimelineSummary(),
41
44
  host: canvasState.getHostCapability(),
42
45
  });