pmx-canvas 0.1.36 → 0.2.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 +447 -0
- package/Readme.md +2 -2
- package/dist/json-render/index.js +89 -334
- package/dist/types/mcp/canvas-access.d.ts +5 -171
- package/dist/types/server/ax-state-manager.d.ts +267 -0
- package/dist/types/server/ax-state.d.ts +3 -1
- package/dist/types/server/canvas-db.d.ts +13 -0
- package/dist/types/server/canvas-operations.d.ts +1 -12
- package/dist/types/server/canvas-state.d.ts +8 -23
- package/dist/types/server/index.d.ts +6 -24
- package/dist/types/server/operations/composites.d.ts +121 -0
- package/dist/types/server/operations/http.d.ts +7 -0
- package/dist/types/server/operations/index.d.ts +8 -0
- package/dist/types/server/operations/invoker.d.ts +13 -0
- package/dist/types/server/operations/mcp.d.ts +15 -0
- package/dist/types/server/operations/ops/annotation.d.ts +2 -0
- package/dist/types/server/operations/ops/app.d.ts +33 -0
- package/dist/types/server/operations/ops/ax-await.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-shared.d.ts +31 -0
- package/dist/types/server/operations/ops/ax-state.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-timeline.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-work.d.ts +2 -0
- package/dist/types/server/operations/ops/batch.d.ts +19 -0
- package/dist/types/server/operations/ops/edges.d.ts +2 -0
- package/dist/types/server/operations/ops/groups.d.ts +2 -0
- package/dist/types/server/operations/ops/json-render.d.ts +31 -0
- package/dist/types/server/operations/ops/nodes.d.ts +62 -0
- package/dist/types/server/operations/ops/query.d.ts +2 -0
- package/dist/types/server/operations/ops/snapshots.d.ts +2 -0
- package/dist/types/server/operations/ops/validate.d.ts +2 -0
- package/dist/types/server/operations/ops/viewport.d.ts +2 -0
- package/dist/types/server/operations/ops/webview.d.ts +2 -0
- package/dist/types/server/operations/registry.d.ts +15 -0
- package/dist/types/server/operations/types.d.ts +116 -0
- package/dist/types/server/operations/webview-runner.d.ts +69 -0
- package/docs/RELEASE.md +5 -0
- package/docs/adr-001-bun-only-runtime.md +46 -0
- package/docs/api-stability.md +57 -0
- package/docs/ax-host-adapter-contract.md +19 -1
- package/docs/ax-state-contract.md +72 -0
- package/docs/http-api.md +4 -0
- package/docs/mcp.md +61 -12
- package/docs/plans/plan-005-operation-registry.md +84 -0
- package/docs/plans/plan-006-mcp-tool-consolidation.md +109 -0
- package/docs/plans/plan-007-ax-domain.md +99 -0
- package/docs/plans/plan-008-registry-finish.md +91 -0
- package/docs/tech-debt-assessment-2026-06.md +90 -0
- package/package.json +3 -3
- package/skills/pmx-canvas/SKILL.md +221 -193
- package/skills/pmx-canvas/evals/evals.json +3 -3
- package/skills/pmx-canvas/references/ax-html-control-surface.md +93 -0
- package/skills/pmx-canvas/references/codex-app-adapter.md +13 -14
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +26 -11
- package/src/cli/agent.ts +52 -31
- package/src/mcp/canvas-access.ts +30 -830
- package/src/mcp/server.ts +162 -2014
- package/src/server/ax-context.ts +8 -1
- package/src/server/ax-state-manager.ts +826 -0
- package/src/server/ax-state.ts +10 -2
- package/src/server/canvas-db.ts +35 -0
- package/src/server/canvas-operations.ts +2 -328
- package/src/server/canvas-schema.ts +2 -2
- package/src/server/canvas-state.ts +103 -465
- package/src/server/index.ts +54 -190
- package/src/server/operations/composites.ts +355 -0
- package/src/server/operations/http.ts +103 -0
- package/src/server/operations/index.ts +65 -0
- package/src/server/operations/invoker.ts +87 -0
- package/src/server/operations/mcp.ts +221 -0
- package/src/server/operations/ops/annotation.ts +60 -0
- package/src/server/operations/ops/app.ts +447 -0
- package/src/server/operations/ops/ax-await.ts +216 -0
- package/src/server/operations/ops/ax-shared.ts +38 -0
- package/src/server/operations/ops/ax-state.ts +249 -0
- package/src/server/operations/ops/ax-timeline.ts +381 -0
- package/src/server/operations/ops/ax-work.ts +635 -0
- package/src/server/operations/ops/batch.ts +365 -0
- package/src/server/operations/ops/edges.ts +166 -0
- package/src/server/operations/ops/groups.ts +176 -0
- package/src/server/operations/ops/json-render.ts +691 -0
- package/src/server/operations/ops/nodes.ts +1047 -0
- package/src/server/operations/ops/query.ts +281 -0
- package/src/server/operations/ops/snapshots.ts +366 -0
- package/src/server/operations/ops/validate.ts +37 -0
- package/src/server/operations/ops/viewport.ts +219 -0
- package/src/server/operations/ops/webview.ts +339 -0
- package/src/server/operations/registry.ts +79 -0
- package/src/server/operations/types.ts +150 -0
- package/src/server/operations/webview-runner.ts +77 -0
- package/src/server/server.ts +158 -2255
- package/src/server/web-artifacts.ts +6 -2
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operation registry core types (plan-005).
|
|
3
|
+
*
|
|
4
|
+
* One `Operation` describes a canvas operation once: input schema, the single
|
|
5
|
+
* handler implementation, and how the operation surfaces over HTTP and MCP.
|
|
6
|
+
* `defineOperation` wraps the typed pieces into a transport-agnostic record.
|
|
7
|
+
*
|
|
8
|
+
* Modules in `operations/` must never import `../server.ts` or `../index.ts`
|
|
9
|
+
* (the SSE emitter is injected via `setOperationEventEmitter`; the SDK imports
|
|
10
|
+
* the operation cores directly).
|
|
11
|
+
*/
|
|
12
|
+
import type { ZodRawShape } from 'zod';
|
|
13
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
14
|
+
|
|
15
|
+
export type OperationErrorStatus = 400 | 404 | 409;
|
|
16
|
+
|
|
17
|
+
/** Operation failure that maps to an HTTP status + `{ ok:false, error }` body and MCP `isError`. */
|
|
18
|
+
export class OperationError extends Error {
|
|
19
|
+
readonly status: OperationErrorStatus;
|
|
20
|
+
/** Extra fields merged into the HTTP `{ ok:false, error }` body (e.g. the legacy
|
|
21
|
+
* webview-failure `webview` status snapshot). Omit for the plain envelope. */
|
|
22
|
+
readonly details?: Record<string, unknown>;
|
|
23
|
+
|
|
24
|
+
constructor(message: string, status: OperationErrorStatus = 400, details?: Record<string, unknown>) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = 'OperationError';
|
|
27
|
+
this.status = status;
|
|
28
|
+
if (details) this.details = details;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface OperationContext {
|
|
33
|
+
/** Emit a workbench SSE event (e.g. extra `canvas-layout-update` frames, focus, viewport). */
|
|
34
|
+
emit(event: string, payload?: Record<string, unknown>): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface OperationHttpRoute {
|
|
38
|
+
method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
|
|
39
|
+
/** EXACT legacy path; `:param` segments capture path parameters. */
|
|
40
|
+
path: string;
|
|
41
|
+
/**
|
|
42
|
+
* Per-op input reader. The default merges query params, a JSON object body
|
|
43
|
+
* (arrays/primitives are preserved by the shared reader — a per-op reader
|
|
44
|
+
* decides how to use them), and path params (params win).
|
|
45
|
+
*/
|
|
46
|
+
readInput?: (
|
|
47
|
+
req: Request,
|
|
48
|
+
params: Record<string, string>,
|
|
49
|
+
url: URL,
|
|
50
|
+
) => Promise<Record<string, unknown>> | Record<string, unknown>;
|
|
51
|
+
/** HTTP status for a successful result. Defaults to 200. */
|
|
52
|
+
status?: (result: unknown) => number;
|
|
53
|
+
/**
|
|
54
|
+
* Return parsed non-2xx JSON bodies to operation callers instead of throwing.
|
|
55
|
+
* Use only for operations whose MCP contract formats structured failure bodies
|
|
56
|
+
* itself (for example canvas.batch partial failures).
|
|
57
|
+
*/
|
|
58
|
+
errorBodyAsResult?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Host capabilities available to MCP result formatters. */
|
|
62
|
+
export interface OperationMcpToolHost {
|
|
63
|
+
getPinnedNodeIds(): Promise<string[]>;
|
|
64
|
+
/**
|
|
65
|
+
* Invoke another registered operation over the host's transport (local or
|
|
66
|
+
* HTTP) — structural subset of OperationInvoker to avoid an import cycle.
|
|
67
|
+
* Used by formatters that need a follow-up read (undo/redo history flags,
|
|
68
|
+
* restore summary).
|
|
69
|
+
*/
|
|
70
|
+
invoker(): { invoke(name: string, input: Record<string, unknown>): Promise<unknown> };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface OperationMcpTool {
|
|
74
|
+
/** Frozen legacy tool name (see tests/unit/mcp-tool-freeze.test.ts). */
|
|
75
|
+
toolName: string;
|
|
76
|
+
description: string;
|
|
77
|
+
/**
|
|
78
|
+
* MCP-only presentation flags and typed overrides merged over the operation
|
|
79
|
+
* input shape when advertising the tool schema (e.g. `full` / `verbose`).
|
|
80
|
+
*/
|
|
81
|
+
extraShape?: ZodRawShape;
|
|
82
|
+
/** Map raw MCP args onto operation input. May throw OperationError. */
|
|
83
|
+
buildInput?: (input: Record<string, unknown>) => Record<string, unknown>;
|
|
84
|
+
/** Format the wire-shaped operation result into a tool result. */
|
|
85
|
+
formatResult?: (
|
|
86
|
+
result: unknown,
|
|
87
|
+
input: Record<string, unknown>,
|
|
88
|
+
host: OperationMcpToolHost,
|
|
89
|
+
) => Promise<CallToolResult> | CallToolResult;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Registered, transport-agnostic operation record. */
|
|
93
|
+
export interface Operation {
|
|
94
|
+
name: string;
|
|
95
|
+
/** true → the registry emits one `canvas-layout-update` after success. */
|
|
96
|
+
mutates: boolean;
|
|
97
|
+
/** Raw zod shape (for MCP tool schemas). */
|
|
98
|
+
inputShape: ZodRawShape;
|
|
99
|
+
http: OperationHttpRoute | null;
|
|
100
|
+
mcp: OperationMcpTool | null;
|
|
101
|
+
/** Validate raw input, run the handler, serialize to the canonical wire shape. */
|
|
102
|
+
execute(rawInput: unknown, ctx: OperationContext): Promise<unknown>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Structural view of a zod schema (avoids fighting zod's generic variance).
|
|
107
|
+
* Any `z.looseObject(...)` satisfies this.
|
|
108
|
+
*/
|
|
109
|
+
export interface OperationInputSchema<I> {
|
|
110
|
+
safeParse(value: unknown):
|
|
111
|
+
| { success: true; data: I }
|
|
112
|
+
| { success: false; error: { issues: Array<{ path: PropertyKey[]; message: string }> } };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface OperationDefinition<I extends Record<string, unknown>, O> {
|
|
116
|
+
name: string;
|
|
117
|
+
mutates: boolean;
|
|
118
|
+
/** MUST be loose (z.looseObject / .passthrough()) — legacy ignores unknown keys. */
|
|
119
|
+
input: OperationInputSchema<I>;
|
|
120
|
+
inputShape: ZodRawShape;
|
|
121
|
+
http?: OperationHttpRoute;
|
|
122
|
+
mcp?: OperationMcpTool;
|
|
123
|
+
/** The single implementation. Mutate via canvasState/canvas-operations so history records. */
|
|
124
|
+
handler: (input: I, ctx: OperationContext) => O | Promise<O>;
|
|
125
|
+
/** Map handler output to the HTTP wire body. Defaults to identity. */
|
|
126
|
+
serialize?: (output: O) => unknown;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function defineOperation<I extends Record<string, unknown>, O>(
|
|
130
|
+
def: OperationDefinition<I, O>,
|
|
131
|
+
): Operation {
|
|
132
|
+
return {
|
|
133
|
+
name: def.name,
|
|
134
|
+
mutates: def.mutates,
|
|
135
|
+
inputShape: def.inputShape,
|
|
136
|
+
http: def.http ?? null,
|
|
137
|
+
mcp: def.mcp ?? null,
|
|
138
|
+
async execute(rawInput: unknown, ctx: OperationContext): Promise<unknown> {
|
|
139
|
+
const parsed = def.input.safeParse(rawInput ?? {});
|
|
140
|
+
if (!parsed.success) {
|
|
141
|
+
const detail = parsed.error.issues
|
|
142
|
+
.map((issue) => (issue.path.length > 0 ? `${issue.path.map(String).join('.')}: ${issue.message}` : issue.message))
|
|
143
|
+
.join('; ');
|
|
144
|
+
throw new OperationError(`Invalid input for ${def.name}: ${detail}`, 400);
|
|
145
|
+
}
|
|
146
|
+
const output = await def.handler(parsed.data, ctx);
|
|
147
|
+
return def.serialize ? def.serialize(output) : output;
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webview runner injection (plan-008 Wave 3).
|
|
3
|
+
*
|
|
4
|
+
* The Bun.WebView automation machinery (startCanvasAutomationWebView / stop /
|
|
5
|
+
* evaluate / resize / status) lives in `../server.ts`, which `operations/` must
|
|
6
|
+
* NEVER import (the isolation rule). The webview ops call into the runner
|
|
7
|
+
* declared here; `server.ts` injects the real implementation at module load via
|
|
8
|
+
* `setWebviewRunner`, exactly mirroring how `setOperationEventEmitter` injects
|
|
9
|
+
* the SSE emitter.
|
|
10
|
+
*
|
|
11
|
+
* `screenshot` is intentionally NOT part of this runner: it returns a binary
|
|
12
|
+
* payload and stays a standalone hand-written tool (`canvas_screenshot`).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Webview status shape (structurally the server.ts CanvasAutomationWebViewStatus). */
|
|
16
|
+
export interface WebviewStatus {
|
|
17
|
+
supported: boolean;
|
|
18
|
+
active: boolean;
|
|
19
|
+
headlessOnly: true;
|
|
20
|
+
url: string | null;
|
|
21
|
+
backend: 'webkit' | 'chrome' | null;
|
|
22
|
+
width: number | null;
|
|
23
|
+
height: number | null;
|
|
24
|
+
dataStoreDir: string | null;
|
|
25
|
+
startedAt: string | null;
|
|
26
|
+
lastError: string | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Start options (structurally the server.ts CanvasAutomationWebViewOptions). */
|
|
30
|
+
export interface WebviewStartOptions {
|
|
31
|
+
backend?: 'webkit' | 'chrome';
|
|
32
|
+
width?: number;
|
|
33
|
+
height?: number;
|
|
34
|
+
chromePath?: string;
|
|
35
|
+
chromeArgv?: string[];
|
|
36
|
+
dataStoreDir?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Outcome of a start attempt, carrying the success/error asymmetry the legacy
|
|
40
|
+
* route preserved:
|
|
41
|
+
* - success → 200 { ok:true, webview }
|
|
42
|
+
* - the canvas server is not running → 503 { ok:false, error } (no webview)
|
|
43
|
+
* - a supported start failed → 500, an unsupported runtime → 501; both return
|
|
44
|
+
* { ok:false, error, webview } and the 500-vs-501 split is read off
|
|
45
|
+
* `webview.supported` (the status), so no separate field is needed. */
|
|
46
|
+
export type WebviewStartResult =
|
|
47
|
+
| { ok: true; webview: WebviewStatus }
|
|
48
|
+
| { ok: false; serverNotRunning: true; error: string }
|
|
49
|
+
| { ok: false; serverNotRunning?: false; error: string; webview: WebviewStatus };
|
|
50
|
+
|
|
51
|
+
export interface WebviewRunner {
|
|
52
|
+
/** Current automation status (never throws). */
|
|
53
|
+
status(): WebviewStatus;
|
|
54
|
+
/** Start or replace the headless automation session for the workbench page. */
|
|
55
|
+
start(options: WebviewStartOptions): Promise<WebviewStartResult>;
|
|
56
|
+
/** Stop the active session (resolves false when none was active). May throw. */
|
|
57
|
+
stop(): Promise<boolean>;
|
|
58
|
+
/** Resize the active viewport. Throws when no session is active. */
|
|
59
|
+
resize(width: number, height: number): Promise<WebviewStatus>;
|
|
60
|
+
/** Evaluate JavaScript in the active page. Throws when no session is active. */
|
|
61
|
+
evaluate(expression: string): Promise<unknown>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let injectedRunner: WebviewRunner | null = null;
|
|
65
|
+
|
|
66
|
+
export function setWebviewRunner(runner: WebviewRunner | null): void {
|
|
67
|
+
injectedRunner = runner;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getWebviewRunner(): WebviewRunner {
|
|
71
|
+
if (!injectedRunner) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
'Webview runner is not injected. server.ts must call setWebviewRunner(...) at module load.',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return injectedRunner;
|
|
77
|
+
}
|