pmx-canvas 0.1.36 → 0.2.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.
- package/CHANGELOG.md +409 -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 +256 -0
- package/dist/types/server/ax-state.d.ts +1 -1
- package/dist/types/server/canvas-operations.d.ts +1 -12
- package/dist/types/server/canvas-state.d.ts +3 -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-state-contract.md +72 -0
- package/docs/mcp.md +60 -11
- 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 +192 -186
- package/skills/pmx-canvas/evals/evals.json +3 -3
- package/skills/pmx-canvas/references/codex-app-adapter.md +13 -14
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +4 -5
- 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-state-manager.ts +808 -0
- package/src/server/ax-state.ts +2 -2
- package/src/server/canvas-operations.ts +2 -328
- package/src/server/canvas-schema.ts +2 -2
- package/src/server/canvas-state.ts +95 -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,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composite MCP tools (plan-006: MCP tool consolidation).
|
|
3
|
+
*
|
|
4
|
+
* A composite tool folds several single-purpose MCP tools into one tool with an
|
|
5
|
+
* `action` discriminator. It is a PRESENTATION-LAYER construct only: each action
|
|
6
|
+
* dispatches to an already-registered operation (`src/server/operations/ops/*`)
|
|
7
|
+
* via the same invoker, reusing that operation's own `mcp.buildInput` and
|
|
8
|
+
* `mcp.formatResult`. So `canvas_edge { action: "add", ... }` is byte-identical
|
|
9
|
+
* to the standalone `canvas_add_edge` — same op, same arg mapping, same result
|
|
10
|
+
* shape — by construction. No handler logic lives here.
|
|
11
|
+
*
|
|
12
|
+
* Migration (docs/api-stability.md + plan-006): composites land ADDITIVELY in
|
|
13
|
+
* v0.2 alongside the legacy single-purpose tools (the tool surface grows, then
|
|
14
|
+
* shrinks when the legacy tools are removed in v0.3). Every action here maps to a
|
|
15
|
+
* registry-backed operation (plan-005 slices 1–7 + plan-008 Wave 1).
|
|
16
|
+
*
|
|
17
|
+
* Still deferred (its legacy standalone tool keeps working; see plan-008): the
|
|
18
|
+
* `canvas_snapshot` composite (the v0.3 name collision). The action enums are
|
|
19
|
+
* forward-compatible: adding an action later is additive. (`canvas_webview`
|
|
20
|
+
* shipped in plan-008 Wave 3 via runner injection; `canvas_app` shipped in Wave 4
|
|
21
|
+
* — open-mcp-app / diagram / build-artifact. Wave 5 folded the last three legacy
|
|
22
|
+
* tools deprecate-only — NO per-action input-injection mechanism was needed:
|
|
23
|
+
* `canvas_add_html_node` / `canvas_add_html_primitive` → `canvas_node` action
|
|
24
|
+
* "add" (type:"html" [+ primitive]); `canvas_refresh_webpage_node` → `canvas_node`
|
|
25
|
+
* action "update" (refresh:true). `canvas_screenshot` stays standalone — it
|
|
26
|
+
* returns a binary image payload the composite/registry JSON wire shape does not
|
|
27
|
+
* model.)
|
|
28
|
+
*
|
|
29
|
+
* Not shipped here: the `canvas_snapshot` composite (plan-006 #7). Its target
|
|
30
|
+
* name is ALREADY a legacy standalone tool (the save-snapshot tool, op
|
|
31
|
+
* `snapshot.save`), so it cannot be added additively without a name clash, and
|
|
32
|
+
* repurposing `canvas_snapshot` to be action-discriminated now would break
|
|
33
|
+
* existing callers. It lands in v0.3, in the same change that removes the legacy
|
|
34
|
+
* single-purpose snapshot tools and frees the name.
|
|
35
|
+
*
|
|
36
|
+
* This module must never import server.ts or index.ts.
|
|
37
|
+
*/
|
|
38
|
+
import { type ZodRawShape } from 'zod';
|
|
39
|
+
/**
|
|
40
|
+
* One composite MCP tool: a frozen tool name + its action→operation routing.
|
|
41
|
+
*
|
|
42
|
+
* Two flavours:
|
|
43
|
+
* - Single-discriminator (the wave-1 composites + the 4 single-discriminator AX
|
|
44
|
+
* composites): the flat `actions` map routes one `action` value → one op.
|
|
45
|
+
* - Two-discriminator (`canvas_ax_gate`, plan-007 Slice C): a `kind` × `action`
|
|
46
|
+
* matrix folds 9 ops into one tool. Set `extraDiscriminatorShape` (the `kind`
|
|
47
|
+
* enum), `memberOps` (the op names — used to derive the schema union + the
|
|
48
|
+
* deprecation notes), `actionEnum` (the action discriminator values), and
|
|
49
|
+
* `resolveOp` (maps `{ kind, action }` → op name, or undefined for an invalid
|
|
50
|
+
* combo → a loud error at dispatch). The flat `actions` map is left empty for
|
|
51
|
+
* these; the matrix path uses `resolveOp` instead.
|
|
52
|
+
*/
|
|
53
|
+
export interface CompositeToolDefinition {
|
|
54
|
+
/** Frozen public tool name (see tests/unit/mcp-tool-freeze.test.ts). */
|
|
55
|
+
toolName: string;
|
|
56
|
+
description: string;
|
|
57
|
+
/** Human-readable action list for the `action` enum description. */
|
|
58
|
+
actionSummary: string;
|
|
59
|
+
/**
|
|
60
|
+
* Map of `action` value → registry operation name (single-discriminator
|
|
61
|
+
* composites). Empty for two-discriminator composites. Every referenced op
|
|
62
|
+
* MUST have an `mcp` block — its `buildInput`/`formatResult` are reused so the
|
|
63
|
+
* composite action matches the legacy standalone tool exactly.
|
|
64
|
+
*/
|
|
65
|
+
actions: Record<string, string>;
|
|
66
|
+
/**
|
|
67
|
+
* Two-discriminator extension (e.g. `canvas_ax_gate`). The extra discriminator
|
|
68
|
+
* shape — a single `kind` enum — merged into the advertised schema alongside
|
|
69
|
+
* `action`.
|
|
70
|
+
*/
|
|
71
|
+
extraDiscriminatorShape?: ZodRawShape;
|
|
72
|
+
/**
|
|
73
|
+
* Two-discriminator extension: the action enum values (used to build the
|
|
74
|
+
* `action` discriminator when there is no flat `actions` map to derive it from).
|
|
75
|
+
*/
|
|
76
|
+
actionEnum?: readonly string[];
|
|
77
|
+
/**
|
|
78
|
+
* Two-discriminator extension: every member op name. Used to build the schema
|
|
79
|
+
* union (all member-op fields, optional) and to derive a deprecation note per
|
|
80
|
+
* member op (each mapped back to its (kind, action) by `describeOp`).
|
|
81
|
+
*/
|
|
82
|
+
memberOps?: string[];
|
|
83
|
+
/**
|
|
84
|
+
* Two-discriminator extension: resolve the op name from the validated
|
|
85
|
+
* discriminators. Returns `undefined` for an invalid combo so dispatch can
|
|
86
|
+
* raise a loud error instead of silently no-op'ing.
|
|
87
|
+
*/
|
|
88
|
+
resolveOp?: (input: {
|
|
89
|
+
kind: string;
|
|
90
|
+
action: string;
|
|
91
|
+
}) => string | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* Two-discriminator extension: human-readable `(kind, action)` for a member op
|
|
94
|
+
* (the inverse of `resolveOp`), used to build that op's deprecation note. The
|
|
95
|
+
* `kind` field-collision is resolved here (see `gateFieldRemap`).
|
|
96
|
+
*/
|
|
97
|
+
describeOp?: (opName: string) => {
|
|
98
|
+
kind: string;
|
|
99
|
+
action: string;
|
|
100
|
+
} | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* Field-name remap applied to the composite's advertised schema and undone at
|
|
103
|
+
* dispatch. Resolves a collision between a discriminator name and a member-op
|
|
104
|
+
* field of the same name (e.g. `ax.approval.request` has its own `action`
|
|
105
|
+
* field — namespaced to `approvalAction` in the composite so the `action`
|
|
106
|
+
* discriminator wins, then mapped back before invoking the op). Keys are the
|
|
107
|
+
* composite (public) field names; values are the op field names.
|
|
108
|
+
*/
|
|
109
|
+
fieldRemap?: Record<string, string>;
|
|
110
|
+
}
|
|
111
|
+
export declare const compositeToolDefinitions: CompositeToolDefinition[];
|
|
112
|
+
/**
|
|
113
|
+
* Deprecation notes for the legacy single-purpose tools, DERIVED from the
|
|
114
|
+
* composites: every operation a composite folds gets a `Deprecated: use
|
|
115
|
+
* canvas_x with action "y".` prefix on its standalone tool description, steering
|
|
116
|
+
* agents to the composite during the v0.2 overlap window (the legacy tools and
|
|
117
|
+
* these notes are removed together in v0.3). Keyed by registry operation name.
|
|
118
|
+
* Deriving it keeps the deprecation list in lockstep with the composites — a new
|
|
119
|
+
* folded action automatically deprecates the tool it replaces.
|
|
120
|
+
*/
|
|
121
|
+
export declare function buildCompositeDeprecationNotes(definitions?: CompositeToolDefinition[]): Map<string, string>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared body reader: preserves the parsed JSON value as-is (object, array,
|
|
3
|
+
* or primitive) — per-op `readInput` decides what to do with non-object
|
|
4
|
+
* bodies; the shared reader never coerces.
|
|
5
|
+
*/
|
|
6
|
+
export declare function readJsonValue(req: Request): Promise<unknown>;
|
|
7
|
+
export declare function dispatchOperationRoute(req: Request, url: URL): Promise<Response | null>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { executeOperation, getOperation, listOperations, registerOperation, setOperationEventEmitter, } from './registry.js';
|
|
2
|
+
export { dispatchOperationRoute } from './http.js';
|
|
3
|
+
export { runCanvasBatchOperation, type BatchEnvelope } from './ops/batch.js';
|
|
4
|
+
export { type OpenMcpAppCoreResult } from './ops/app.js';
|
|
5
|
+
export { LocalOperationInvoker, HttpOperationInvoker, type OperationInvoker } from './invoker.js';
|
|
6
|
+
export { registerOperationTools, registerCompositeTools, type OperationToolHost } from './mcp.js';
|
|
7
|
+
export { compositeToolDefinitions, type CompositeToolDefinition } from './composites.js';
|
|
8
|
+
export { OperationError, defineOperation, type Operation, type OperationContext, type OperationDefinition, type OperationErrorStatus, } from './types.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface OperationInvoker {
|
|
2
|
+
invoke(name: string, input: Record<string, unknown>): Promise<unknown>;
|
|
3
|
+
}
|
|
4
|
+
/** Runs operations in-process against the shared canvasState singleton. */
|
|
5
|
+
export declare class LocalOperationInvoker implements OperationInvoker {
|
|
6
|
+
invoke(name: string, input: Record<string, unknown>): Promise<unknown>;
|
|
7
|
+
}
|
|
8
|
+
/** Builds the HTTP request from the op's route template (`:id` from input, GET flags to query). */
|
|
9
|
+
export declare class HttpOperationInvoker implements OperationInvoker {
|
|
10
|
+
private readonly baseUrl;
|
|
11
|
+
constructor(baseUrl: string);
|
|
12
|
+
invoke(name: string, input: Record<string, unknown>): Promise<unknown>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { OperationInvoker } from './invoker.js';
|
|
3
|
+
import { type OperationMcpToolHost } from './types.js';
|
|
4
|
+
import { type CompositeToolDefinition } from './composites.js';
|
|
5
|
+
export interface OperationToolHost extends OperationMcpToolHost {
|
|
6
|
+
invoker(): OperationInvoker;
|
|
7
|
+
}
|
|
8
|
+
export declare function registerOperationTools(server: McpServer, getHost: () => Promise<OperationToolHost>): void;
|
|
9
|
+
/**
|
|
10
|
+
* Register composite (action-discriminated) MCP tools (plan-006). Each action
|
|
11
|
+
* dispatches to a registered operation, reusing that op's `mcp.buildInput` and
|
|
12
|
+
* `mcp.formatResult` so the composite action is byte-identical to the standalone
|
|
13
|
+
* tool it folds. Defaults to `compositeToolDefinitions`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function registerCompositeTools(server: McpServer, getHost: () => Promise<OperationToolHost>, definitions?: CompositeToolDefinition[]): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ExternalMcpTransportConfig } from '../../mcp-app-runtime.js';
|
|
2
|
+
import { type Operation, type OperationContext } from '../types.js';
|
|
3
|
+
export interface OpenMcpAppCoreInput {
|
|
4
|
+
transport: ExternalMcpTransportConfig;
|
|
5
|
+
toolName: string;
|
|
6
|
+
toolArguments?: Record<string, unknown>;
|
|
7
|
+
nodeId?: string;
|
|
8
|
+
serverName?: string;
|
|
9
|
+
title?: string;
|
|
10
|
+
x?: number;
|
|
11
|
+
y?: number;
|
|
12
|
+
width?: number;
|
|
13
|
+
height?: number;
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface OpenMcpAppCoreResult {
|
|
17
|
+
ok: true;
|
|
18
|
+
id?: string;
|
|
19
|
+
nodeId: string | null;
|
|
20
|
+
toolCallId: string;
|
|
21
|
+
sessionId: string;
|
|
22
|
+
resourceUri: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Open an external MCP app: connect + call + read resource (openExternalMcpApp),
|
|
26
|
+
* close any prior session on an in-place node, emit `ext-app-open` +
|
|
27
|
+
* `ext-app-result`, then resolve the resulting canvas node id. This is the exact
|
|
28
|
+
* legacy SDK `openMcpApp` body, relocated; both the mcpapp.open op AND the SDK
|
|
29
|
+
* call it. The diagram.open op delegates here after building the Excalidraw input
|
|
30
|
+
* (the SSE pair fires ONCE — diagram.open does not re-emit).
|
|
31
|
+
*/
|
|
32
|
+
export declare function openMcpAppCore(input: OpenMcpAppCoreInput, ctx: OperationContext): Promise<OpenMcpAppCoreResult>;
|
|
33
|
+
export declare const appOperations: Operation[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the AX operation modules (ax-state.ts, ax-work.ts, and the
|
|
3
|
+
* later timeline/delivery waves). These were replicated per-file during the
|
|
4
|
+
* plan-007 Slice B migration; centralizing them here keeps one definition site
|
|
5
|
+
* as more AX op files land.
|
|
6
|
+
*
|
|
7
|
+
* `normalizeAxSource` / `normalizeAxNodeIds` are reimplemented from server.ts
|
|
8
|
+
* because `operations/` must never import server.ts (the SSE emitter is injected;
|
|
9
|
+
* see plan-005). This module likewise must not import server.ts or index.ts.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
+
import type { PmxAxSource } from '../../ax-state.js';
|
|
14
|
+
export declare const AX_SOURCES: readonly ["agent", "api", "browser", "cli", "codex", "copilot", "mcp", "sdk", "system"];
|
|
15
|
+
/** Zod schema for the optional `source` field shared by AX MCP tool shapes. */
|
|
16
|
+
export declare const AX_SOURCE_SHAPE: z.ZodOptional<z.ZodEnum<{
|
|
17
|
+
agent: "agent";
|
|
18
|
+
api: "api";
|
|
19
|
+
browser: "browser";
|
|
20
|
+
cli: "cli";
|
|
21
|
+
codex: "codex";
|
|
22
|
+
copilot: "copilot";
|
|
23
|
+
mcp: "mcp";
|
|
24
|
+
sdk: "sdk";
|
|
25
|
+
system: "system";
|
|
26
|
+
}>>;
|
|
27
|
+
/** An absent or unrecognized source falls back to the per-surface default. */
|
|
28
|
+
export declare function normalizeAxSource(value: unknown, fallback: PmxAxSource): PmxAxSource;
|
|
29
|
+
export declare function normalizeAxNodeIds(value: unknown): string[];
|
|
30
|
+
/** The plain JSON tool result shared by AX ops whose MCP body is the wire body. */
|
|
31
|
+
export declare function axJsonResult(result: unknown): CallToolResult;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Operation } from '../types.js';
|
|
2
|
+
export interface BatchEnvelope {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
results: Array<Record<string, unknown>>;
|
|
5
|
+
refs: Record<string, unknown>;
|
|
6
|
+
failedIndex?: number;
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const batchOperations: Operation[];
|
|
10
|
+
/**
|
|
11
|
+
* Typed SDK entry point: run a batch through the registry's single execution
|
|
12
|
+
* path (`executeOperation('canvas.batch')`). The op emits the one final
|
|
13
|
+
* canvas-layout-update itself, so callers must NOT emit again.
|
|
14
|
+
*/
|
|
15
|
+
export declare function runCanvasBatchOperation(operations: Array<{
|
|
16
|
+
op: string;
|
|
17
|
+
assign?: string;
|
|
18
|
+
args?: Record<string, unknown>;
|
|
19
|
+
}>): Promise<BatchEnvelope>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Operation } from '../types.js';
|
|
2
|
+
/** Legacy server.ts parseGraphPayloadData: a graph dataset must be an array of records. */
|
|
3
|
+
export declare function parseGraphPayloadData(value: unknown): Array<Record<string, unknown>> | null;
|
|
4
|
+
export interface StreamJsonRenderInput {
|
|
5
|
+
nodeId?: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
patches?: unknown[];
|
|
8
|
+
done?: boolean;
|
|
9
|
+
x?: number;
|
|
10
|
+
y?: number;
|
|
11
|
+
width?: number;
|
|
12
|
+
height?: number;
|
|
13
|
+
strictSize?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface StreamJsonRenderResult {
|
|
16
|
+
id: string;
|
|
17
|
+
url: string;
|
|
18
|
+
ok: true;
|
|
19
|
+
applied: number;
|
|
20
|
+
skipped: number;
|
|
21
|
+
specVersion: number;
|
|
22
|
+
elementCount: number;
|
|
23
|
+
streamStatus: 'open' | 'closed';
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create-or-append core for streaming json-render nodes (the SDK's
|
|
27
|
+
* streamJsonRenderNode wraps this directly). Throws OperationError(400) when
|
|
28
|
+
* the append target is missing or not a json-render node.
|
|
29
|
+
*/
|
|
30
|
+
export declare function streamJsonRenderCore(input: StreamJsonRenderInput): StreamJsonRenderResult;
|
|
31
|
+
export declare const jsonRenderOperations: Operation[];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type CanvasLayout, type CanvasNodeState } from '../../canvas-state.js';
|
|
2
|
+
import { type Operation } from '../types.js';
|
|
3
|
+
export declare const NODE_TYPES: readonly ["markdown", "status", "context", "ledger", "trace", "file", "image", "mcp-app", "webpage", "html", "group"];
|
|
4
|
+
/** Per-type default node frame size (formerly copy-pasted ladders). */
|
|
5
|
+
export declare function defaultNodeSize(type: string): {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
10
|
+
export declare function pickFiniteNumber(record: Record<string, unknown>, key: string): number | undefined;
|
|
11
|
+
export declare function getRecord(value: unknown): Record<string, unknown> | undefined;
|
|
12
|
+
export declare function pickPositiveNumber(record: Record<string, unknown>, key: string): number | undefined;
|
|
13
|
+
export declare function resolveCreateGeometry(body: Record<string, unknown>): {
|
|
14
|
+
x?: number;
|
|
15
|
+
y?: number;
|
|
16
|
+
width?: number;
|
|
17
|
+
height?: number;
|
|
18
|
+
};
|
|
19
|
+
export declare function setGroupChildrenFromApi(groupId: string, childIds: string[]): boolean;
|
|
20
|
+
export declare function nodeAppSessionId(node: CanvasNodeState | undefined): string | null;
|
|
21
|
+
export declare function closeNodeAppSession(node: CanvasNodeState | undefined): void;
|
|
22
|
+
export declare function buildNodeResponse(node: CanvasNodeState): Record<string, unknown>;
|
|
23
|
+
export declare function wantsFullPayload(input?: Record<string, unknown>): boolean;
|
|
24
|
+
export declare function compactNodePayload(node: CanvasNodeState | undefined): Record<string, unknown> | null;
|
|
25
|
+
export declare function buildSummaryFromLayout(layout: CanvasLayout, pinnedIds: string[]): Record<string, unknown>;
|
|
26
|
+
export declare function compactLayoutPayload(layout: CanvasLayout, pinnedIds: string[]): Record<string, unknown>;
|
|
27
|
+
export declare function agentSafeFullLayoutPayload(layout: CanvasLayout): Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* Node-create/update MCP payload: exposes both `id` and a `nodeId` alias so
|
|
30
|
+
* agents using either key (or a cached schema) work — matching the
|
|
31
|
+
* external-app / web-artifact responses that already return both.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createdNodePayloadFromNode(node: CanvasNodeState, options?: Record<string, unknown>): Record<string, unknown>;
|
|
34
|
+
/**
|
|
35
|
+
* Create a basic (non-webpage / non-group / non-primitive) node. Union of the
|
|
36
|
+
* legacy handleCanvasAddNode generic branch; the SDK passes fileMode 'path',
|
|
37
|
+
* the HTTP/MCP operation passes fileMode 'auto'.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createBasicCanvasNode(body: Record<string, unknown>, options: {
|
|
40
|
+
fileMode: 'auto' | 'path';
|
|
41
|
+
}): {
|
|
42
|
+
node: CanvasNodeState;
|
|
43
|
+
needsCodeGraphRecompute: boolean;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Build a node patch with the full HTTP superset semantics (webpage
|
|
47
|
+
* titleSource, html top-level fields, axCapabilities merge, group children,
|
|
48
|
+
* structured spec/graph updates, trace fields). Throws OperationError on
|
|
49
|
+
* validation failures. The SDK's updateNode delegates here.
|
|
50
|
+
*/
|
|
51
|
+
export declare function buildNodePatch(existing: CanvasNodeState, body: Record<string, unknown>): {
|
|
52
|
+
patch: Partial<CanvasNodeState>;
|
|
53
|
+
groupChildIds?: string[];
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Remove a node (closing any mcp-app session). Missing id → OperationError 404
|
|
57
|
+
* on ALL surfaces (plan-005 deliberately unifies the old silent local success).
|
|
58
|
+
*/
|
|
59
|
+
export declare function removeNodeCore(id: string): {
|
|
60
|
+
needsCodeGraphRecompute: boolean;
|
|
61
|
+
};
|
|
62
|
+
export declare const nodeOperations: Operation[];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Operation } from './types.js';
|
|
2
|
+
export declare function registerOperation(op: Operation): void;
|
|
3
|
+
export declare function getOperation(name: string): Operation;
|
|
4
|
+
export declare function listOperations(): Operation[];
|
|
5
|
+
type OperationEventEmitter = (event: string, payload: Record<string, unknown>) => void;
|
|
6
|
+
export declare function setOperationEventEmitter(emitter: OperationEventEmitter | null): void;
|
|
7
|
+
/** True while operation SSE emits are being suppressed (inside a meta-op such as
|
|
8
|
+
* canvas.batch). Ops whose effect depends on a live SSE emit firing — e.g.
|
|
9
|
+
* mcpapp.open, whose canvas node is created as a side-effect of `ext-app-open` —
|
|
10
|
+
* use this to reject loudly instead of silently no-op'ing in a suppressed run. */
|
|
11
|
+
export declare function isEmitSuppressed(): boolean;
|
|
12
|
+
/** Run `fn` with all operation SSE emits suppressed; restores depth on finally. */
|
|
13
|
+
export declare function runWithSuppressedEmits<T>(fn: () => Promise<T>): Promise<T>;
|
|
14
|
+
export declare function executeOperation(name: string, rawInput: unknown): Promise<unknown>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
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
|
+
export type OperationErrorStatus = 400 | 404 | 409;
|
|
15
|
+
/** Operation failure that maps to an HTTP status + `{ ok:false, error }` body and MCP `isError`. */
|
|
16
|
+
export declare class OperationError extends Error {
|
|
17
|
+
readonly status: OperationErrorStatus;
|
|
18
|
+
/** Extra fields merged into the HTTP `{ ok:false, error }` body (e.g. the legacy
|
|
19
|
+
* webview-failure `webview` status snapshot). Omit for the plain envelope. */
|
|
20
|
+
readonly details?: Record<string, unknown>;
|
|
21
|
+
constructor(message: string, status?: OperationErrorStatus, details?: Record<string, unknown>);
|
|
22
|
+
}
|
|
23
|
+
export interface OperationContext {
|
|
24
|
+
/** Emit a workbench SSE event (e.g. extra `canvas-layout-update` frames, focus, viewport). */
|
|
25
|
+
emit(event: string, payload?: Record<string, unknown>): void;
|
|
26
|
+
}
|
|
27
|
+
export interface OperationHttpRoute {
|
|
28
|
+
method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
|
|
29
|
+
/** EXACT legacy path; `:param` segments capture path parameters. */
|
|
30
|
+
path: string;
|
|
31
|
+
/**
|
|
32
|
+
* Per-op input reader. The default merges query params, a JSON object body
|
|
33
|
+
* (arrays/primitives are preserved by the shared reader — a per-op reader
|
|
34
|
+
* decides how to use them), and path params (params win).
|
|
35
|
+
*/
|
|
36
|
+
readInput?: (req: Request, params: Record<string, string>, url: URL) => Promise<Record<string, unknown>> | Record<string, unknown>;
|
|
37
|
+
/** HTTP status for a successful result. Defaults to 200. */
|
|
38
|
+
status?: (result: unknown) => number;
|
|
39
|
+
/**
|
|
40
|
+
* Return parsed non-2xx JSON bodies to operation callers instead of throwing.
|
|
41
|
+
* Use only for operations whose MCP contract formats structured failure bodies
|
|
42
|
+
* itself (for example canvas.batch partial failures).
|
|
43
|
+
*/
|
|
44
|
+
errorBodyAsResult?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** Host capabilities available to MCP result formatters. */
|
|
47
|
+
export interface OperationMcpToolHost {
|
|
48
|
+
getPinnedNodeIds(): Promise<string[]>;
|
|
49
|
+
/**
|
|
50
|
+
* Invoke another registered operation over the host's transport (local or
|
|
51
|
+
* HTTP) — structural subset of OperationInvoker to avoid an import cycle.
|
|
52
|
+
* Used by formatters that need a follow-up read (undo/redo history flags,
|
|
53
|
+
* restore summary).
|
|
54
|
+
*/
|
|
55
|
+
invoker(): {
|
|
56
|
+
invoke(name: string, input: Record<string, unknown>): Promise<unknown>;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export interface OperationMcpTool {
|
|
60
|
+
/** Frozen legacy tool name (see tests/unit/mcp-tool-freeze.test.ts). */
|
|
61
|
+
toolName: string;
|
|
62
|
+
description: string;
|
|
63
|
+
/**
|
|
64
|
+
* MCP-only presentation flags and typed overrides merged over the operation
|
|
65
|
+
* input shape when advertising the tool schema (e.g. `full` / `verbose`).
|
|
66
|
+
*/
|
|
67
|
+
extraShape?: ZodRawShape;
|
|
68
|
+
/** Map raw MCP args onto operation input. May throw OperationError. */
|
|
69
|
+
buildInput?: (input: Record<string, unknown>) => Record<string, unknown>;
|
|
70
|
+
/** Format the wire-shaped operation result into a tool result. */
|
|
71
|
+
formatResult?: (result: unknown, input: Record<string, unknown>, host: OperationMcpToolHost) => Promise<CallToolResult> | CallToolResult;
|
|
72
|
+
}
|
|
73
|
+
/** Registered, transport-agnostic operation record. */
|
|
74
|
+
export interface Operation {
|
|
75
|
+
name: string;
|
|
76
|
+
/** true → the registry emits one `canvas-layout-update` after success. */
|
|
77
|
+
mutates: boolean;
|
|
78
|
+
/** Raw zod shape (for MCP tool schemas). */
|
|
79
|
+
inputShape: ZodRawShape;
|
|
80
|
+
http: OperationHttpRoute | null;
|
|
81
|
+
mcp: OperationMcpTool | null;
|
|
82
|
+
/** Validate raw input, run the handler, serialize to the canonical wire shape. */
|
|
83
|
+
execute(rawInput: unknown, ctx: OperationContext): Promise<unknown>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Structural view of a zod schema (avoids fighting zod's generic variance).
|
|
87
|
+
* Any `z.looseObject(...)` satisfies this.
|
|
88
|
+
*/
|
|
89
|
+
export interface OperationInputSchema<I> {
|
|
90
|
+
safeParse(value: unknown): {
|
|
91
|
+
success: true;
|
|
92
|
+
data: I;
|
|
93
|
+
} | {
|
|
94
|
+
success: false;
|
|
95
|
+
error: {
|
|
96
|
+
issues: Array<{
|
|
97
|
+
path: PropertyKey[];
|
|
98
|
+
message: string;
|
|
99
|
+
}>;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export interface OperationDefinition<I extends Record<string, unknown>, O> {
|
|
104
|
+
name: string;
|
|
105
|
+
mutates: boolean;
|
|
106
|
+
/** MUST be loose (z.looseObject / .passthrough()) — legacy ignores unknown keys. */
|
|
107
|
+
input: OperationInputSchema<I>;
|
|
108
|
+
inputShape: ZodRawShape;
|
|
109
|
+
http?: OperationHttpRoute;
|
|
110
|
+
mcp?: OperationMcpTool;
|
|
111
|
+
/** The single implementation. Mutate via canvasState/canvas-operations so history records. */
|
|
112
|
+
handler: (input: I, ctx: OperationContext) => O | Promise<O>;
|
|
113
|
+
/** Map handler output to the HTTP wire body. Defaults to identity. */
|
|
114
|
+
serialize?: (output: O) => unknown;
|
|
115
|
+
}
|
|
116
|
+
export declare function defineOperation<I extends Record<string, unknown>, O>(def: OperationDefinition<I, O>): Operation;
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
/** Webview status shape (structurally the server.ts CanvasAutomationWebViewStatus). */
|
|
15
|
+
export interface WebviewStatus {
|
|
16
|
+
supported: boolean;
|
|
17
|
+
active: boolean;
|
|
18
|
+
headlessOnly: true;
|
|
19
|
+
url: string | null;
|
|
20
|
+
backend: 'webkit' | 'chrome' | null;
|
|
21
|
+
width: number | null;
|
|
22
|
+
height: number | null;
|
|
23
|
+
dataStoreDir: string | null;
|
|
24
|
+
startedAt: string | null;
|
|
25
|
+
lastError: string | null;
|
|
26
|
+
}
|
|
27
|
+
/** Start options (structurally the server.ts CanvasAutomationWebViewOptions). */
|
|
28
|
+
export interface WebviewStartOptions {
|
|
29
|
+
backend?: 'webkit' | 'chrome';
|
|
30
|
+
width?: number;
|
|
31
|
+
height?: number;
|
|
32
|
+
chromePath?: string;
|
|
33
|
+
chromeArgv?: string[];
|
|
34
|
+
dataStoreDir?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Outcome of a start attempt, carrying the success/error asymmetry the legacy
|
|
37
|
+
* route preserved:
|
|
38
|
+
* - success → 200 { ok:true, webview }
|
|
39
|
+
* - the canvas server is not running → 503 { ok:false, error } (no webview)
|
|
40
|
+
* - a supported start failed → 500, an unsupported runtime → 501; both return
|
|
41
|
+
* { ok:false, error, webview } and the 500-vs-501 split is read off
|
|
42
|
+
* `webview.supported` (the status), so no separate field is needed. */
|
|
43
|
+
export type WebviewStartResult = {
|
|
44
|
+
ok: true;
|
|
45
|
+
webview: WebviewStatus;
|
|
46
|
+
} | {
|
|
47
|
+
ok: false;
|
|
48
|
+
serverNotRunning: true;
|
|
49
|
+
error: string;
|
|
50
|
+
} | {
|
|
51
|
+
ok: false;
|
|
52
|
+
serverNotRunning?: false;
|
|
53
|
+
error: string;
|
|
54
|
+
webview: WebviewStatus;
|
|
55
|
+
};
|
|
56
|
+
export interface WebviewRunner {
|
|
57
|
+
/** Current automation status (never throws). */
|
|
58
|
+
status(): WebviewStatus;
|
|
59
|
+
/** Start or replace the headless automation session for the workbench page. */
|
|
60
|
+
start(options: WebviewStartOptions): Promise<WebviewStartResult>;
|
|
61
|
+
/** Stop the active session (resolves false when none was active). May throw. */
|
|
62
|
+
stop(): Promise<boolean>;
|
|
63
|
+
/** Resize the active viewport. Throws when no session is active. */
|
|
64
|
+
resize(width: number, height: number): Promise<WebviewStatus>;
|
|
65
|
+
/** Evaluate JavaScript in the active page. Throws when no session is active. */
|
|
66
|
+
evaluate(expression: string): Promise<unknown>;
|
|
67
|
+
}
|
|
68
|
+
export declare function setWebviewRunner(runner: WebviewRunner | null): void;
|
|
69
|
+
export declare function getWebviewRunner(): WebviewRunner;
|
package/docs/RELEASE.md
CHANGED
|
@@ -35,6 +35,11 @@ bun run release:smoke # packs + boots from a clean dir
|
|
|
35
35
|
bun run pack:dry-run # confirms the tarball shape
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
`bun run test:web-canvas` invokes Playwright through
|
|
39
|
+
`scripts/run-playwright.sh`, which runs the Playwright CLI under Node —
|
|
40
|
+
do not call `bun x playwright test` directly; it fails before test
|
|
41
|
+
discovery with a `.esm.preflight` loader error (ERR-20260508-001).
|
|
42
|
+
|
|
38
43
|
`bun run test:e2e-cli` starts a local server in a fresh temp workspace
|
|
39
44
|
and exercises the CLI flows from
|
|
40
45
|
[`docs/evals/e2e-cli-coverage.md`](evals/e2e-cli-coverage.md).
|