pmx-canvas 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +9 -3
  2. package/.github/extensions/pmx-canvas/runtime.json +1 -1
  3. package/.github/extensions/pmx-canvas/tools.json +9278 -2014
  4. package/CHANGELOG.md +34 -1
  5. package/dist/canvas/global.css +12 -17
  6. package/dist/canvas/index.js +58 -58
  7. package/dist/canvas/mcp-app.html +75 -80
  8. package/dist/types/client/canvas/submit-without-navigation.d.ts +3 -0
  9. package/dist/types/client/state/intent-bridge.d.ts +0 -3
  10. package/dist/types/client/state/presence-store.d.ts +3 -3
  11. package/dist/types/client/types.d.ts +1 -1
  12. package/dist/types/mcp/canvas-access.d.ts +1 -12
  13. package/dist/types/mcp/resource-subscriptions.d.ts +7 -0
  14. package/dist/types/mcp/server.d.ts +2 -0
  15. package/dist/types/server/ax-wait.d.ts +1 -0
  16. package/dist/types/server/canvas-operations.d.ts +1 -0
  17. package/dist/types/server/canvas-state.d.ts +2 -0
  18. package/dist/types/server/index.d.ts +5 -5
  19. package/dist/types/server/knowledge-lifecycle.d.ts +11 -2
  20. package/dist/types/server/lifecycle-status.d.ts +3 -1
  21. package/dist/types/server/operations/http.d.ts +10 -0
  22. package/dist/types/server/operations/index.d.ts +2 -2
  23. package/dist/types/server/operations/invoker.d.ts +4 -3
  24. package/dist/types/server/operations/mcp-contracts.d.ts +20 -0
  25. package/dist/types/server/operations/mcp.d.ts +2 -0
  26. package/dist/types/server/operations/ops/batch.d.ts +1 -0
  27. package/dist/types/server/operations/registry.d.ts +2 -2
  28. package/dist/types/server/operations/types.d.ts +12 -2
  29. package/dist/types/server/server.d.ts +2 -0
  30. package/dist/types/server/tour-control.d.ts +6 -3
  31. package/dist/types/server/webpage-node.d.ts +1 -0
  32. package/docs/api-reference.md +2 -2
  33. package/docs/cli.md +10 -1
  34. package/docs/design/Chrome.dc.html +1 -0
  35. package/docs/design.md +3 -1
  36. package/docs/http-api.md +31 -0
  37. package/docs/mcp.md +98 -22
  38. package/docs/node-types.md +3 -0
  39. package/docs/plans/plan-019-post-0.9-roadmap.md +187 -11
  40. package/docs/sdk.md +10 -0
  41. package/package.json +1 -1
  42. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +9 -3
  43. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  44. package/plugins/pmx-canvas/plugin.json +1 -1
  45. package/plugins/pmx-canvas/runtime.json +1 -1
  46. package/plugins/pmx-canvas/tools.json +9278 -2014
  47. package/skills/pmx-canvas/SKILL.md +12 -7
  48. package/skills/pmx-canvas/references/full-reference.md +35 -2
  49. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
  50. package/skills/pmx-canvas/references/installing-pmx-canvas.md +7 -7
  51. package/src/client/canvas/BoardCopyDialog.tsx +2 -1
  52. package/src/client/canvas/KnowledgeDialog.tsx +3 -0
  53. package/src/client/canvas/LifecyclePanel.tsx +4 -0
  54. package/src/client/canvas/RelationPresetsDialog.tsx +2 -0
  55. package/src/client/canvas/ToolRail.tsx +2 -0
  56. package/src/client/canvas/submit-without-navigation.ts +14 -0
  57. package/src/client/state/canvas-store.ts +12 -24
  58. package/src/client/state/intent-bridge.ts +0 -6
  59. package/src/client/state/presence-store.ts +3 -3
  60. package/src/client/state/sse-bridge.ts +11 -6
  61. package/src/client/theme/global.css +12 -17
  62. package/src/mcp/canvas-access.ts +6 -47
  63. package/src/mcp/resource-subscriptions.ts +181 -0
  64. package/src/mcp/server.ts +185 -219
  65. package/src/server/ax-wait.ts +21 -6
  66. package/src/server/canvas-operations.ts +6 -2
  67. package/src/server/canvas-state.ts +10 -0
  68. package/src/server/index.ts +20 -32
  69. package/src/server/knowledge-lifecycle.ts +66 -0
  70. package/src/server/lifecycle-status.ts +79 -7
  71. package/src/server/operations/http.ts +69 -21
  72. package/src/server/operations/index.ts +8 -1
  73. package/src/server/operations/invoker.ts +53 -5
  74. package/src/server/operations/mcp-contracts.ts +339 -0
  75. package/src/server/operations/mcp.ts +153 -18
  76. package/src/server/operations/ops/ax-await.ts +18 -10
  77. package/src/server/operations/ops/ax-read.ts +2 -0
  78. package/src/server/operations/ops/ax-timeline.ts +2 -2
  79. package/src/server/operations/ops/batch.ts +27 -8
  80. package/src/server/operations/ops/lifecycle.ts +3 -7
  81. package/src/server/operations/ops/nodes.ts +20 -8
  82. package/src/server/operations/ops/viewport.ts +3 -7
  83. package/src/server/operations/registry.ts +24 -4
  84. package/src/server/operations/types.ts +16 -2
  85. package/src/server/server.ts +23 -10
  86. package/src/server/tour-control.ts +17 -8
  87. package/src/server/webpage-node.ts +9 -4
@@ -0,0 +1,181 @@
1
+ import { setTimeout as delay } from 'node:timers/promises';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import {
4
+ ErrorCode,
5
+ McpError,
6
+ SubscribeRequestSchema,
7
+ UnsubscribeRequestSchema,
8
+ } from '@modelcontextprotocol/sdk/types.js';
9
+ import { canvasState } from '../server/canvas-state.js';
10
+ import { onWorkbenchEvent } from '../server/server.js';
11
+ import type { CanvasAccess } from './canvas-access.js';
12
+ import { ContextUriTemplate } from './context-uri-template.js';
13
+
14
+ const liveResources = new Set(
15
+ [
16
+ 'layout',
17
+ 'boards',
18
+ 'summary',
19
+ 'context',
20
+ 'graph',
21
+ 'spatial-context',
22
+ 'history',
23
+ 'code-graph',
24
+ 'pinned-context',
25
+ 'ax',
26
+ 'ax-context',
27
+ 'ax-timeline',
28
+ 'ax-work',
29
+ 'ax-pending-steering',
30
+ 'ax-delivery',
31
+ ].map((name) => `canvas://${name}`),
32
+ );
33
+ const contextTemplate = new ContextUriTemplate();
34
+ const graphTemplate = new ContextUriTemplate('graph', ['focus', 'depth']);
35
+
36
+ // A context-status-changed read receipt must never trigger another read forever.
37
+ const resourceEvents = new Set([
38
+ 'context-pins-changed',
39
+ 'boards-changed',
40
+ 'knowledge-changed',
41
+ 'ax-state-changed',
42
+ 'ax-event-created',
43
+ 'canvas-layout-update',
44
+ 'canvas-viewport-update',
45
+ 'canvas-camera-move',
46
+ 'canvas-tour-step',
47
+ 'canvas-tour-exit',
48
+ // These events synchronize node data inside server.ts.
49
+ 'workbench-open',
50
+ 'ext-app-open',
51
+ 'ext-app-update',
52
+ 'ext-app-result',
53
+ 'context-cards',
54
+ 'context-usage',
55
+ 'aux-open',
56
+ 'aux-close',
57
+ 'canvas-status',
58
+ 'execution-phase',
59
+ 'canvas-response-start',
60
+ 'canvas-response-delta',
61
+ 'canvas-response-complete',
62
+ ]);
63
+
64
+ function isLiveResource(uri: string): boolean {
65
+ return liveResources.has(uri) || contextTemplate.match(uri) !== null || graphTemplate.match(uri) !== null;
66
+ }
67
+
68
+ /** One subscription set and event source per MCP connection. Notifications are not reads. */
69
+ export function registerResourceSubscriptions(server: McpServer, getCanvas: () => Promise<CanvasAccess>) {
70
+ const subscriptions = new Set<string>();
71
+ let disposeSource: (() => void) | undefined;
72
+ let source: string | null | undefined;
73
+ let closed = false;
74
+
75
+ function notify(): void {
76
+ // Broad invalidation is intentional: context projections span pins, nodes and AX.
77
+ for (const uri of subscriptions) {
78
+ void server.server.sendResourceUpdated({ uri }).catch((error: unknown) => {
79
+ if (!closed) console.error('[mcp] resource notification failed:', error);
80
+ });
81
+ }
82
+ }
83
+
84
+ async function watchRemote(baseUrl: string, signal: AbortSignal): Promise<void> {
85
+ while (!signal.aborted) {
86
+ try {
87
+ const response = await fetch(`${baseUrl}/api/workbench/events`, { signal });
88
+ if (!response.ok || !response.body) throw new Error(`Canvas event stream: HTTP ${response.status}`);
89
+ const reader = response.body.getReader();
90
+ const decoder = new TextDecoder();
91
+ let buffer = '';
92
+ try {
93
+ // Reconnecting may have missed a change. Invalidate subscribed URIs only.
94
+ notify();
95
+ while (!signal.aborted) {
96
+ const { done, value } = await reader.read();
97
+ if (done) break;
98
+ buffer += decoder.decode(value, { stream: true });
99
+ const frames = buffer.split('\n\n');
100
+ buffer = frames.pop() ?? '';
101
+ for (const frame of frames) {
102
+ const event = frame
103
+ .split('\n')
104
+ .find((line) => line.startsWith('event:'))
105
+ ?.slice(6)
106
+ .trim();
107
+ if (event && resourceEvents.has(event)) notify();
108
+ }
109
+ }
110
+ } finally {
111
+ await reader.cancel().catch(() => {});
112
+ reader.releaseLock();
113
+ }
114
+ } catch (error) {
115
+ if (signal.aborted) return;
116
+ console.error('[mcp] canvas event stream disconnected:', error);
117
+ }
118
+ try {
119
+ await delay(1_000, undefined, { signal });
120
+ } catch {
121
+ return;
122
+ }
123
+ }
124
+ }
125
+
126
+ function attach(canvas: CanvasAccess): void {
127
+ if (closed || subscriptions.size === 0 || source === canvas.remoteBaseUrl) return;
128
+ disposeSource?.();
129
+ source = canvas.remoteBaseUrl;
130
+ if (source) {
131
+ const controller = new AbortController();
132
+ disposeSource = () => controller.abort();
133
+ void watchRemote(source, controller.signal);
134
+ } else {
135
+ const disposeState = canvasState.onChange(notify);
136
+ // Library-only writes do not change the active board's state.
137
+ const disposeEvents = onWorkbenchEvent((event) => {
138
+ if (resourceEvents.has(event)) notify();
139
+ });
140
+ disposeSource = () => {
141
+ disposeState();
142
+ disposeEvents();
143
+ };
144
+ }
145
+ }
146
+
147
+ function dispose(): void {
148
+ closed = true;
149
+ subscriptions.clear();
150
+ disposeSource?.();
151
+ disposeSource = undefined;
152
+ source = undefined;
153
+ }
154
+
155
+ server.server.registerCapabilities({ resources: { subscribe: true } });
156
+ server.server.setRequestHandler(SubscribeRequestSchema, async ({ params }, extra) => {
157
+ if (!isLiveResource(params.uri))
158
+ throw new McpError(ErrorCode.InvalidParams, `Resource does not support subscriptions: ${params.uri}`);
159
+ const canvas = await getCanvas();
160
+ extra.signal.throwIfAborted();
161
+ if (closed) throw new McpError(ErrorCode.InternalError, 'Connection closed');
162
+ subscriptions.add(params.uri);
163
+ attach(canvas);
164
+ return {};
165
+ });
166
+ server.server.setRequestHandler(UnsubscribeRequestSchema, ({ params }) => {
167
+ subscriptions.delete(params.uri);
168
+ if (subscriptions.size === 0) {
169
+ disposeSource?.();
170
+ disposeSource = undefined;
171
+ source = undefined;
172
+ }
173
+ return {};
174
+ });
175
+ const onclose = server.server.onclose;
176
+ server.server.onclose = () => {
177
+ dispose();
178
+ onclose?.();
179
+ };
180
+ return { attach, dispose };
181
+ }