pmx-canvas 0.4.4 → 0.4.6
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 +108 -0
- package/Readme.md +52 -4
- package/dist/canvas/global.css +115 -11
- package/dist/canvas/index.js +93 -72
- package/dist/canvas/mermaid-entry.js +3563 -0
- package/dist/canvas/surface-theme.css +2 -2
- package/dist/json-render/index.css +1 -1
- package/dist/types/cli/agent.d.ts +1 -0
- package/dist/types/cli/commands/skills.d.ts +1 -1
- package/dist/types/cli/commands/smoke.d.ts +1 -0
- package/dist/types/client/mermaid-entry.d.ts +11 -0
- package/dist/types/client/nodes/DiffNode.d.ts +20 -0
- package/dist/types/client/nodes/ExtAppFrame.d.ts +0 -2
- package/dist/types/client/nodes/MermaidNode.d.ts +12 -0
- package/dist/types/client/state/intent-store.d.ts +6 -0
- package/dist/types/client/types.d.ts +2 -2
- package/dist/types/server/ax-state-manager.d.ts +5 -0
- package/dist/types/server/ax-state.d.ts +9 -3
- package/dist/types/server/canvas-db.d.ts +3 -2
- package/dist/types/server/canvas-provenance.d.ts +1 -1
- package/dist/types/server/canvas-state.d.ts +22 -1
- package/dist/types/server/canvas-validation.d.ts +34 -1
- package/dist/types/server/html-surface.d.ts +8 -0
- package/dist/types/server/index.d.ts +5 -0
- package/dist/types/server/operations/ops/ax-shared.d.ts +4 -1
- package/dist/types/server/operations/ops/nodes.d.ts +1 -1
- package/dist/types/server/operations/registry.d.ts +9 -1
- package/dist/types/server/workboard.d.ts +46 -0
- package/dist/types/shared/ax-intent.d.ts +6 -0
- package/dist/types/shared/ax-surface-protocol.d.ts +7 -0
- package/docs/cli.md +12 -0
- package/docs/environment.md +1 -0
- package/docs/http-api.md +12 -0
- package/docs/mcp.md +1 -1
- package/docs/node-types.md +2 -0
- package/docs/screenshot.png +0 -0
- package/docs/screenshots/amp-orb-portal.png +0 -0
- package/docs/screenshots/claude-code-desktop.png +0 -0
- package/docs/screenshots/codex-app.png +0 -0
- package/docs/screenshots/github-copilot-app.png +0 -0
- package/package.json +4 -3
- package/skills/pmx-canvas/SKILL.md +71 -24
- package/skills/pmx-canvas-orchestration/SKILL.md +82 -0
- package/skills/pmx-canvas-testing/SKILL.md +3 -0
- package/src/cli/agent.ts +2 -0
- package/src/cli/commands/ax.ts +28 -3
- package/src/cli/commands/skills.ts +1 -1
- package/src/cli/commands/smoke.ts +212 -0
- package/src/cli/index.ts +15 -2
- package/src/cli/shared.ts +1 -0
- package/src/client/canvas/CanvasNode.tsx +12 -1
- package/src/client/canvas/CanvasViewport.tsx +6 -0
- package/src/client/canvas/ExpandedNodeOverlay.tsx +10 -0
- package/src/client/canvas/IntentLayer.tsx +8 -6
- package/src/client/canvas/Minimap.tsx +2 -0
- package/src/client/canvas/auto-fit.ts +2 -0
- package/src/client/icons.tsx +4 -0
- package/src/client/mermaid-entry.ts +76 -0
- package/src/client/nodes/DiffNode.tsx +114 -0
- package/src/client/nodes/ExtAppFrame.tsx +259 -84
- package/src/client/nodes/MermaidNode.tsx +81 -0
- package/src/client/state/intent-bridge.ts +12 -3
- package/src/client/state/intent-store.ts +47 -2
- package/src/client/state/node-factory.ts +2 -0
- package/src/client/state/sse-bridge.ts +28 -0
- package/src/client/theme/global.css +115 -11
- package/src/client/theme/surface-theme.css +2 -2
- package/src/client/types.ts +6 -0
- package/src/mcp/server.ts +22 -6
- package/src/server/ax-interaction.ts +6 -0
- package/src/server/ax-state-manager.ts +27 -6
- package/src/server/ax-state.ts +36 -3
- package/src/server/canvas-db.ts +83 -60
- package/src/server/canvas-operations.ts +39 -9
- package/src/server/canvas-provenance.ts +2 -0
- package/src/server/canvas-schema.ts +33 -0
- package/src/server/canvas-state.ts +78 -6
- package/src/server/canvas-validation.ts +79 -0
- package/src/server/html-surface.ts +11 -0
- package/src/server/index.ts +25 -6
- package/src/server/intent-registry.ts +2 -0
- package/src/server/operations/composites.ts +3 -2
- package/src/server/operations/http.ts +5 -1
- package/src/server/operations/ops/ax-shared.ts +20 -1
- package/src/server/operations/ops/ax-state.ts +4 -7
- package/src/server/operations/ops/ax-timeline.ts +26 -3
- package/src/server/operations/ops/ax-work.ts +27 -1
- package/src/server/operations/ops/batch.ts +11 -1
- package/src/server/operations/ops/json-render.ts +44 -0
- package/src/server/operations/ops/nodes.ts +24 -2
- package/src/server/operations/ops/viewport.ts +8 -1
- package/src/server/operations/registry.ts +122 -1
- package/src/server/server.ts +58 -3
- package/src/server/workboard.ts +159 -0
- package/src/shared/ax-intent.ts +6 -0
- package/src/shared/ax-surface-protocol.ts +8 -0
- package/src/shared/surface.ts +2 -0
- package/src/shared/themes.ts +1 -1
|
@@ -56,7 +56,8 @@ export declare function loadPendingAxSteeringFromDB(db: Database, options?: {
|
|
|
56
56
|
/**
|
|
57
57
|
* NEWEST undelivered steering first (report #57) for the compact AX context lead
|
|
58
58
|
* block — so a fresh steer is visible even behind a long backlog. Loop-safe: excludes
|
|
59
|
-
* the consumer's own steering
|
|
59
|
+
* the consumer's own steering and steering targeted at a different consumer in SQL
|
|
60
|
+
* so the LIMIT applies after both filters.
|
|
60
61
|
* Distinct from loadPendingAxSteeringFromDB (FIFO oldest-first) which the claim/ack
|
|
61
62
|
* delivery queue uses for ordered processing.
|
|
62
63
|
*/
|
|
@@ -64,7 +65,7 @@ export declare function loadNewestPendingAxSteeringFromDB(db: Database, options?
|
|
|
64
65
|
consumer?: string;
|
|
65
66
|
limit?: number;
|
|
66
67
|
}): PmxAxSteeringMessage[];
|
|
67
|
-
/** Total undelivered steering for a consumer (loop-safe — excludes the consumer's own). */
|
|
68
|
+
/** Total undelivered steering for a consumer (loop-safe — excludes the consumer's own; target-scoped). */
|
|
68
69
|
export declare function countPendingAxSteeringFromDB(db: Database, consumer?: string): number;
|
|
69
70
|
export declare function loadAxTimelineSummaryFromDB(db: Database): PmxAxTimelineSummary;
|
|
70
71
|
export declare function upsertAxHostCapabilityToDB(db: Database, cap: PmxAxHostCapability): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type CanvasNodeType = 'markdown' | 'mcp-app' | 'webpage' | 'json-render' | 'graph' | 'prompt' | 'response' | 'status' | 'context' | 'ledger' | 'trace' | 'file' | 'image' | 'html' | 'group';
|
|
1
|
+
export type CanvasNodeType = 'markdown' | 'mcp-app' | 'webpage' | 'json-render' | 'graph' | 'prompt' | 'response' | 'status' | 'context' | 'ledger' | 'trace' | 'file' | 'diff' | 'mermaid' | 'image' | 'html' | 'group';
|
|
2
2
|
export type CanvasNodeProvenanceSourceKind = 'workspace-file' | 'webpage-url' | 'mcp-tool' | 'artifact-file' | 'image-url';
|
|
3
3
|
export type CanvasNodeRefreshStrategy = 'file-watch' | 'file-read-write' | 'image-reload' | 'webpage-refresh' | 'mcp-app-rehydrate' | 'artifact-reopen';
|
|
4
4
|
export interface CanvasNodeProvenance {
|
|
@@ -53,7 +53,7 @@ export interface CanvasSnapshotGcResult {
|
|
|
53
53
|
}
|
|
54
54
|
export interface CanvasNodeState {
|
|
55
55
|
id: string;
|
|
56
|
-
type: 'markdown' | 'mcp-app' | 'webpage' | 'json-render' | 'graph' | 'prompt' | 'response' | 'status' | 'context' | 'ledger' | 'trace' | 'file' | 'image' | 'html' | 'group';
|
|
56
|
+
type: 'markdown' | 'mcp-app' | 'webpage' | 'json-render' | 'graph' | 'prompt' | 'response' | 'status' | 'context' | 'ledger' | 'trace' | 'file' | 'diff' | 'mermaid' | 'image' | 'html' | 'group';
|
|
57
57
|
position: {
|
|
58
58
|
x: number;
|
|
59
59
|
y: number;
|
|
@@ -154,6 +154,15 @@ declare class CanvasStateManager {
|
|
|
154
154
|
* — e.g. the long-lived MCP subscription — may ignore the return value).
|
|
155
155
|
*/
|
|
156
156
|
onChange(cb: (type: CanvasChangeType) => void): () => void;
|
|
157
|
+
private _workItemsChangedListener;
|
|
158
|
+
/**
|
|
159
|
+
* Register THE work-item change listener (single slot, last-write-wins).
|
|
160
|
+
* Fired after addWorkItem/updateWorkItem completes (including the node
|
|
161
|
+
* status mirror), so live views like the workboard can rebuild from the
|
|
162
|
+
* fresh work-item list.
|
|
163
|
+
*/
|
|
164
|
+
setWorkItemsChangedListener(listener: (() => void) | null): void;
|
|
165
|
+
private notifyWorkItemsChanged;
|
|
157
166
|
private notifyChange;
|
|
158
167
|
private _mutationRecorder;
|
|
159
168
|
private _suppressRecordingDepth;
|
|
@@ -280,6 +289,7 @@ declare class CanvasStateManager {
|
|
|
280
289
|
status?: PmxAxWorkItemStatus;
|
|
281
290
|
detail?: string | null;
|
|
282
291
|
nodeIds?: string[];
|
|
292
|
+
agentId?: string | null;
|
|
283
293
|
}, options?: {
|
|
284
294
|
source?: PmxAxSource;
|
|
285
295
|
}): PmxAxWorkItem;
|
|
@@ -288,9 +298,17 @@ declare class CanvasStateManager {
|
|
|
288
298
|
status?: PmxAxWorkItemStatus;
|
|
289
299
|
detail?: string | null;
|
|
290
300
|
nodeIds?: string[];
|
|
301
|
+
agentId?: string | null;
|
|
291
302
|
}, options?: {
|
|
292
303
|
source?: PmxAxSource;
|
|
293
304
|
}): PmxAxWorkItem | null;
|
|
305
|
+
/**
|
|
306
|
+
* Mirror a work item's status onto every linked canvas node's `data.axWorkStatus`,
|
|
307
|
+
* through the standard node-update path (persistence + undo/redo, same as any other
|
|
308
|
+
* node data change). Nodes unlinked by an updateWorkItem nodeIds change have the
|
|
309
|
+
* mirrored status cleared.
|
|
310
|
+
*/
|
|
311
|
+
private mirrorAxWorkStatusToNodes;
|
|
294
312
|
getApprovalGates(): PmxAxApprovalGate[];
|
|
295
313
|
requestApproval(input: {
|
|
296
314
|
title: string;
|
|
@@ -376,6 +394,7 @@ declare class CanvasStateManager {
|
|
|
376
394
|
data?: Record<string, unknown> | null;
|
|
377
395
|
}, options?: {
|
|
378
396
|
source?: PmxAxSource;
|
|
397
|
+
agentId?: string | null;
|
|
379
398
|
}): PmxAxEvent;
|
|
380
399
|
addEvidence(input: {
|
|
381
400
|
kind: PmxAxEvidenceKind;
|
|
@@ -389,6 +408,8 @@ declare class CanvasStateManager {
|
|
|
389
408
|
}): PmxAxEvidence;
|
|
390
409
|
recordSteeringMessage(message: string, options?: {
|
|
391
410
|
source?: PmxAxSource;
|
|
411
|
+
agentId?: string | null;
|
|
412
|
+
target?: string | null;
|
|
392
413
|
}): PmxAxSteeringMessage;
|
|
393
414
|
markSteeringDelivered(id: string): boolean;
|
|
394
415
|
ingestActivity(input: {
|
|
@@ -1,4 +1,25 @@
|
|
|
1
|
-
import type { CanvasLayout } from './canvas-state.js';
|
|
1
|
+
import type { CanvasLayout, CanvasNodeState } from './canvas-state.js';
|
|
2
|
+
/**
|
|
3
|
+
* Per-type minimum node size (0.4.5 report follow-up): agents frequently
|
|
4
|
+
* create nodes with explicit frames far too small for their content — clipped
|
|
5
|
+
* markdown, charts squeezed behind inner scrollbars. Explicit sizes below the
|
|
6
|
+
* floor are clamped UP at creation (canvas-operations.ts creators);
|
|
7
|
+
* `strictSize: true` is the escape hatch for a genuinely small fixed frame.
|
|
8
|
+
* Floors sit below every type default and above the point of unreadability.
|
|
9
|
+
* Creation-only: later updates are untouched (the browser's drag-resize
|
|
10
|
+
* enforces its own 200×100 floor client-side), but `validate` reports any
|
|
11
|
+
* node below its floor as an advisory sizeWarning. Absent types (trace,
|
|
12
|
+
* group, prompt/response) are intentionally unclamped — trace is small by
|
|
13
|
+
* design, groups size to their children.
|
|
14
|
+
*/
|
|
15
|
+
export declare const NODE_MIN_CREATE_SIZES: Partial<Record<CanvasNodeState['type'], {
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
}>>;
|
|
19
|
+
export declare function clampCreateNodeSize(type: CanvasNodeState['type'], width: number, height: number, strictSize?: boolean): {
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
};
|
|
2
23
|
export interface CanvasValidationPair {
|
|
3
24
|
aId: string;
|
|
4
25
|
aTitle: string | null;
|
|
@@ -11,6 +32,15 @@ export interface CanvasContainmentIssue {
|
|
|
11
32
|
childId: string;
|
|
12
33
|
childTitle: string | null;
|
|
13
34
|
}
|
|
35
|
+
export interface CanvasSizeWarning {
|
|
36
|
+
id: string;
|
|
37
|
+
title: string | null;
|
|
38
|
+
type: CanvasNodeState['type'];
|
|
39
|
+
width: number;
|
|
40
|
+
height: number;
|
|
41
|
+
minWidth: number;
|
|
42
|
+
minHeight: number;
|
|
43
|
+
}
|
|
14
44
|
export interface CanvasValidationResult {
|
|
15
45
|
ok: boolean;
|
|
16
46
|
collisions: CanvasValidationPair[];
|
|
@@ -21,6 +51,8 @@ export interface CanvasValidationResult {
|
|
|
21
51
|
from: string;
|
|
22
52
|
to: string;
|
|
23
53
|
}>;
|
|
54
|
+
/** Nodes below their type's readable minimum (advisory — does not fail `ok`). */
|
|
55
|
+
sizeWarnings: CanvasSizeWarning[];
|
|
24
56
|
summary: {
|
|
25
57
|
nodes: number;
|
|
26
58
|
edges: number;
|
|
@@ -28,6 +60,7 @@ export interface CanvasValidationResult {
|
|
|
28
60
|
containments: number;
|
|
29
61
|
containmentViolations: number;
|
|
30
62
|
missingEdgeEndpoints: number;
|
|
63
|
+
sizeWarnings: number;
|
|
31
64
|
};
|
|
32
65
|
}
|
|
33
66
|
export declare function validateCanvasLayout(layout: CanvasLayout): CanvasValidationResult;
|
|
@@ -53,6 +53,14 @@ export declare function buildAxStateBridge(axToken: string, snapshotJson: string
|
|
|
53
53
|
* and the json-render injection site stay byte-identical (no drift).
|
|
54
54
|
*/
|
|
55
55
|
export declare function buildContentHeightReporter(frameToken: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Author-HTML fragment for a `mermaid` node surface: the HTML-escaped diagram
|
|
58
|
+
* source in a hidden `<pre>` the entry script reads back via textContent, plus
|
|
59
|
+
* the separately-built renderer bundle (dist/canvas/mermaid-entry.js — kept out
|
|
60
|
+
* of the main SPA bundle). Escaping is mandatory: the source is arbitrary node
|
|
61
|
+
* data and must not be parsed as markup.
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildMermaidSurfaceHtml(source: string): string;
|
|
56
64
|
export interface HtmlSurfaceOptions {
|
|
57
65
|
theme: SurfaceTheme;
|
|
58
66
|
/**
|
|
@@ -161,9 +161,12 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
161
161
|
data?: Record<string, unknown> | null;
|
|
162
162
|
}, options?: {
|
|
163
163
|
source?: PmxAxSource;
|
|
164
|
+
agentId?: string | null;
|
|
164
165
|
}): PmxAxEvent;
|
|
165
166
|
sendSteering(message: string, options?: {
|
|
166
167
|
source?: PmxAxSource;
|
|
168
|
+
agentId?: string | null;
|
|
169
|
+
target?: string | null;
|
|
167
170
|
}): PmxAxSteeringMessage;
|
|
168
171
|
markSteeringDelivered(id: string): boolean;
|
|
169
172
|
/**
|
|
@@ -198,6 +201,7 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
198
201
|
status?: PmxAxWorkItemStatus;
|
|
199
202
|
detail?: string | null;
|
|
200
203
|
nodeIds?: string[];
|
|
204
|
+
agentId?: string | null;
|
|
201
205
|
}, options?: {
|
|
202
206
|
source?: PmxAxSource;
|
|
203
207
|
}): PmxAxWorkItem;
|
|
@@ -206,6 +210,7 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
206
210
|
status?: PmxAxWorkItemStatus;
|
|
207
211
|
detail?: string | null;
|
|
208
212
|
nodeIds?: string[];
|
|
213
|
+
agentId?: string | null;
|
|
209
214
|
}, options?: {
|
|
210
215
|
source?: PmxAxSource;
|
|
211
216
|
}): PmxAxWorkItem | null;
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
import { z } from 'zod';
|
|
12
12
|
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
13
13
|
import type { PmxAxSource } from '../../ax-state.js';
|
|
14
|
-
export declare const AX_SOURCES: readonly ["agent", "api", "browser", "cli", "codex", "copilot", "mcp", "sdk", "system"];
|
|
14
|
+
export declare const AX_SOURCES: readonly ["agent", "amp", "api", "browser", "cli", "codex", "copilot", "mcp", "sdk", "system"];
|
|
15
15
|
/** Zod schema for the optional `source` field shared by AX MCP tool shapes. */
|
|
16
16
|
export declare const AX_SOURCE_SHAPE: z.ZodOptional<z.ZodEnum<{
|
|
17
|
+
amp: "amp";
|
|
17
18
|
agent: "agent";
|
|
18
19
|
api: "api";
|
|
19
20
|
browser: "browser";
|
|
@@ -24,6 +25,8 @@ export declare const AX_SOURCE_SHAPE: z.ZodOptional<z.ZodEnum<{
|
|
|
24
25
|
sdk: "sdk";
|
|
25
26
|
system: "system";
|
|
26
27
|
}>>;
|
|
28
|
+
/** Zod schema for the optional `agentId` field shared by AX MCP tool shapes. */
|
|
29
|
+
export declare const AX_AGENT_ID_SHAPE: z.ZodOptional<z.ZodString>;
|
|
27
30
|
/** An absent or unrecognized source falls back to the per-surface default. */
|
|
28
31
|
export declare function normalizeAxSource(value: unknown, fallback: PmxAxSource): PmxAxSource;
|
|
29
32
|
export declare function normalizeAxNodeIds(value: unknown): string[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type CanvasLayout, type CanvasNodeState } from '../../canvas-state.js';
|
|
2
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"];
|
|
3
|
+
export declare const NODE_TYPES: readonly ["markdown", "status", "context", "ledger", "trace", "file", "diff", "mermaid", "image", "mcp-app", "webpage", "html", "group"];
|
|
4
4
|
/** Per-type default node frame size (formerly copy-pasted ladders). */
|
|
5
5
|
export declare function defaultNodeSize(type: string): {
|
|
6
6
|
width: number;
|
|
@@ -11,5 +11,13 @@ export declare function setOperationEventEmitter(emitter: OperationEventEmitter
|
|
|
11
11
|
export declare function isEmitSuppressed(): boolean;
|
|
12
12
|
/** Run `fn` with all operation SSE emits suppressed; restores depth on finally. */
|
|
13
13
|
export declare function runWithSuppressedEmits<T>(fn: () => Promise<T>): Promise<T>;
|
|
14
|
-
export
|
|
14
|
+
export interface ExecuteOperationMeta {
|
|
15
|
+
/**
|
|
16
|
+
* Skip the synthesized auto-ghost: set by the workbench's own HTTP calls
|
|
17
|
+
* (a human dragging a node is not agent activity) and by canvas.batch for
|
|
18
|
+
* its inner dispatches (batch churn is exempt, matching the skill contract).
|
|
19
|
+
*/
|
|
20
|
+
suppressAutoGhost?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare function executeOperation(name: string, rawInput: unknown, meta?: ExecuteOperationMeta): Promise<unknown>;
|
|
15
23
|
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live AX work-item board, materialized as a json-render node.
|
|
3
|
+
*
|
|
4
|
+
* `buildWorkboardSpec` is the pure spec builder (status columns of work-item
|
|
5
|
+
* cards); `renderWorkboard` is the operation core (create-or-rebuild the
|
|
6
|
+
* single `data.workboard === true` json-render node); `refreshWorkboardNodes`
|
|
7
|
+
* is the live-refresh hook this module registers on `canvasState` so every
|
|
8
|
+
* addWorkItem/updateWorkItem rebuilds the board from the fresh work-item list
|
|
9
|
+
* through the SAME spec/update path the operation uses.
|
|
10
|
+
*
|
|
11
|
+
* The refresh runs inside `withSuppressedRecording` so live board rebuilds do
|
|
12
|
+
* not spam undo/redo history (persistence and SSE emits are unaffected).
|
|
13
|
+
*/
|
|
14
|
+
import type { JsonRenderSpec } from '../json-render/server.js';
|
|
15
|
+
import type { PmxAxWorkItem } from './ax-state.js';
|
|
16
|
+
export declare const WORKBOARD_NODE_TITLE = "Work Board";
|
|
17
|
+
export declare const WORKBOARD_NODE_SIZE: {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Build the workboard json-render spec from a work-item list: one column per
|
|
23
|
+
* status (todo → in-progress → blocked → done → cancelled; empty statuses are
|
|
24
|
+
* omitted), each item a Card with an agentId Badge and detail Text when
|
|
25
|
+
* present. An empty list renders a single muted "No work items" text block.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildWorkboardSpec(workItems: PmxAxWorkItem[]): JsonRenderSpec;
|
|
28
|
+
/**
|
|
29
|
+
* Operation core for `render.workboard`: rebuild the existing workboard node
|
|
30
|
+
* in place, or create one (tagged `data.workboard: true`) when none exists.
|
|
31
|
+
*/
|
|
32
|
+
export declare function renderWorkboard(input?: {
|
|
33
|
+
x?: number;
|
|
34
|
+
y?: number;
|
|
35
|
+
}): {
|
|
36
|
+
ok: true;
|
|
37
|
+
id: string;
|
|
38
|
+
created: boolean;
|
|
39
|
+
itemCount: number;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Live refresh: rebuild every workboard node's spec from the current work-item
|
|
43
|
+
* list. Registered below as the canvasState work-item change listener. Node
|
|
44
|
+
* updates never touch work items, so this cannot recurse.
|
|
45
|
+
*/
|
|
46
|
+
export declare function refreshWorkboardNodes(): void;
|
|
@@ -43,6 +43,12 @@ export interface PmxAxIntent {
|
|
|
43
43
|
seq?: number;
|
|
44
44
|
/** Source label of the surface that signalled the intent (mcp/api/sdk/...). */
|
|
45
45
|
source?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Server-synthesized ghost for an agent mutation that arrived WITHOUT an
|
|
48
|
+
* explicit canvas_intent signal (auto-ghost). Rendered visually lighter than
|
|
49
|
+
* a deliberate signal, and not vetoable — it settles immediately.
|
|
50
|
+
*/
|
|
51
|
+
auto?: boolean;
|
|
46
52
|
/** Epoch ms when the intent was first signalled. */
|
|
47
53
|
createdAt: number;
|
|
48
54
|
/** Epoch ms when the intent auto-dissolves if not settled/cleared first. */
|
|
@@ -16,3 +16,10 @@ export declare const AX_SURFACE_ACK_SOURCE = "pmx-canvas-ax-ack";
|
|
|
16
16
|
export declare const HTML_SURFACE_PUSH_SOURCE = "pmx-canvas-html-node";
|
|
17
17
|
/** Ext-app boot beacon: the iframe's scripts executed (WebKit watchdog liveness). */
|
|
18
18
|
export declare const EXT_APP_BOOT_BEACON_SOURCE = "pmx-canvas-ext-app-alive";
|
|
19
|
+
/**
|
|
20
|
+
* Parent→ext-app paint probe (Finding N): the parent asks the app document to
|
|
21
|
+
* answer with a double-rAF `paint-tick` beacon. A frame whose rendering
|
|
22
|
+
* pipeline is live answers within a frame or two; silence past the timeout is
|
|
23
|
+
* the paint-fail signal that drives the recovery ladder.
|
|
24
|
+
*/
|
|
25
|
+
export declare const EXT_APP_PAINT_PROBE_SOURCE = "pmx-canvas-ext-app-paint-probe";
|
package/docs/cli.md
CHANGED
|
@@ -201,6 +201,18 @@ pmx-canvas ax policy get
|
|
|
201
201
|
pmx-canvas ax policy set --excluded-tools shell,write --mode concise
|
|
202
202
|
```
|
|
203
203
|
|
|
204
|
+
## Environment smoke check
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
pmx-canvas smoke # one-command check of a running canvas
|
|
208
|
+
pmx-canvas smoke --port 4750 # target a specific daemon
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Verifies server health + workspace, CLI/server version skew, the MCP
|
|
212
|
+
initialize handshake, a temporary node create/search/remove round-trip (the
|
|
213
|
+
temp node is always removed, including on failure), and board validation.
|
|
214
|
+
Prints a JSON report and exits 1 if any check fails.
|
|
215
|
+
|
|
204
216
|
## Copilot adapter
|
|
205
217
|
|
|
206
218
|
Install the bundled GitHub Copilot extension adapter into a repo. The adapter
|
package/docs/environment.md
CHANGED
|
@@ -27,6 +27,7 @@ optional; defaults are listed.
|
|
|
27
27
|
|---|---|---|
|
|
28
28
|
| `PMX_CANVAS_THEME` | server | Startup theme: `dark` (default), `light`, `high-contrast`, `midnight`, `sepia`, `arctic`, `ember`, `forest`, `volt`. |
|
|
29
29
|
| `AMP_ORB` | server | Set by Amp orb services. When present, the served canvas page tells the browser it is running in an orb, which skips the iframe embed probe, forces `srcdoc` surface rendering when embedded (the orb portal blocks `src`-URL child iframes and the probe is unreliable there), and defaults the event transport to polling instead of SSE (the portal proxy buffers streams; `?transport=sse` overrides for diagnosis). |
|
|
30
|
+
| `PORT` | server | Portal-assigned service port, honored ONLY when `AMP_ORB` is also set (Amp orbs) — after `--port`, `PMX_WEB_CANVAS_PORT`, and `PMX_CANVAS_PORT`, before the 4313 default. A stray `PORT` in normal shells never changes the port. |
|
|
30
31
|
| `PMX_CANVAS_DISABLE_BROWSER_OPEN` | server | `1` suppresses auto-opening the browser (used by tests/CI). |
|
|
31
32
|
| `PMX_CANVAS_DIST` | server | Explicit client bundle directory to serve instead of the packaged `dist/canvas`. |
|
|
32
33
|
| `PMX_CANVAS_WEBVIEW_TIMEOUT_MS` | server | Startup timeout for the Bun.WebView automation session. |
|
package/docs/http-api.md
CHANGED
|
@@ -306,6 +306,18 @@ curl -X DELETE "http://localhost:4313/api/canvas/ax/intent/<id>?settledNodeId=no
|
|
|
306
306
|
curl -X DELETE "http://localhost:4313/api/canvas/ax/intent/<id>?vetoed=true"
|
|
307
307
|
```
|
|
308
308
|
|
|
309
|
+
## Live work board
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
# Materialize (or refresh) the one board node showing all AX work items by status.
|
|
313
|
+
curl -X POST http://localhost:4313/api/canvas/workboard \
|
|
314
|
+
-H 'Content-Type: application/json' -d '{}'
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
The board is a `json-render` node tagged `data.workboard: true`. Repeat calls
|
|
318
|
+
are idempotent (`created: false`) and the board refreshes itself whenever a
|
|
319
|
+
work item changes — no polling needed.
|
|
320
|
+
|
|
309
321
|
## Diagrams (Excalidraw preset)
|
|
310
322
|
|
|
311
323
|
```bash
|
package/docs/mcp.md
CHANGED
|
@@ -38,7 +38,7 @@ its `action` to the same operation the legacy tool used, so results are identica
|
|
|
38
38
|
| Composite | `action` values | Replaced (removed in v0.3.0) |
|
|
39
39
|
|-----------|-----------------|----------|
|
|
40
40
|
| `canvas_node` | `add` · `get` · `update` · `remove` | `canvas_add_node`, `canvas_get_node`, `canvas_update_node`, `canvas_remove_node`, `canvas_add_html_node` (`add` + `type:"html"`), `canvas_add_html_primitive` (`add` + `type:"html"`, `primitive:"<kind>"`), `canvas_refresh_webpage_node` (`update` + `refresh:true`) |
|
|
41
|
-
| `canvas_render` | `describe-schema` · `validate` · `add-json-render` · `stream-json-render` · `add-graph` | `canvas_describe_schema`, `canvas_validate_spec`, `canvas_add_json_render_node`, `canvas_stream_json_render_node`, `canvas_add_graph_node` |
|
|
41
|
+
| `canvas_render` | `describe-schema` · `validate` · `add-json-render` · `stream-json-render` · `add-graph` · `workboard` | `canvas_describe_schema`, `canvas_validate_spec`, `canvas_add_json_render_node`, `canvas_stream_json_render_node`, `canvas_add_graph_node` |
|
|
42
42
|
| `canvas_edge` | `add` · `remove` | `canvas_add_edge`, `canvas_remove_edge` |
|
|
43
43
|
| `canvas_group` | `create` · `add` · `ungroup` | `canvas_create_group`, `canvas_group_nodes`, `canvas_ungroup` |
|
|
44
44
|
| `canvas_history` | `undo` · `redo` | `canvas_undo`, `canvas_redo` |
|
package/docs/node-types.md
CHANGED
|
@@ -15,6 +15,8 @@ see [MCP tools](mcp.md), [HTTP API](http-api.md), and [SDK](sdk.md).
|
|
|
15
15
|
| `ledger` | Execution ledger summary |
|
|
16
16
|
| `trace` | Agent trace pills (tool calls, subagent activity) |
|
|
17
17
|
| `file` | Live file viewer with auto-update on disk changes |
|
|
18
|
+
| `diff` | Unified diff viewer: per-file headers, hunk rows, +/− colored lines |
|
|
19
|
+
| `mermaid` | Mermaid diagram rendered client-side from text (flowchart, sequence, state, …) |
|
|
18
20
|
| `image` | Image viewer (file paths, data URIs, URLs) |
|
|
19
21
|
| `webpage` | Persisted webpage snapshot with stored URL, extracted text, refresh |
|
|
20
22
|
| `mcp-app` | Tool-backed hosted MCP App iframes (Excalidraw, etc.) |
|
package/docs/screenshot.png
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmx-canvas",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Spatial canvas workbench for coding agents
|
|
3
|
+
"version": "0.4.6",
|
|
4
|
+
"description": "Spatial canvas workbench for coding agents — infinite 2D canvas with agent-native CLI, MCP integration, nodes, edges, file watching, and snapshots",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/server/index.ts",
|
|
7
7
|
"module": "./src/server/index.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"dev:demo": "bun run src/cli/index.ts --demo",
|
|
33
33
|
"dev:portless": "portless run --name pmx --app-port 4313 bun run src/cli/index.ts --no-open --port=4313",
|
|
34
34
|
"dev:portless:demo": "portless run --name pmx --app-port 4313 bun run src/cli/index.ts --no-open --port=4313 --demo",
|
|
35
|
-
"build:client": "bun build src/client/index.tsx --outdir dist/canvas --minify && cp src/client/theme/global.css dist/canvas/global.css && cp src/client/theme/surface-theme.css dist/canvas/surface-theme.css",
|
|
35
|
+
"build:client": "bun build src/client/index.tsx --outdir dist/canvas --minify && bun build src/client/mermaid-entry.ts --outdir dist/canvas --minify && cp src/client/theme/global.css dist/canvas/global.css && cp src/client/theme/surface-theme.css dist/canvas/surface-theme.css",
|
|
36
36
|
"build:json-render": "bash scripts/build-json-render.sh",
|
|
37
37
|
"build:types": "tsc -p tsconfig.types.json",
|
|
38
38
|
"build": "bun run build:client && bun run build:json-render && bun run build:types",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
69
69
|
"@preact/signals": "^2.0.0",
|
|
70
70
|
"marked": "^15.0.0",
|
|
71
|
+
"mermaid": "^11.16.1",
|
|
71
72
|
"preact": "^10.25.0",
|
|
72
73
|
"react": "^19.2.3",
|
|
73
74
|
"react-dom": "^19.2.3",
|
|
@@ -44,9 +44,18 @@ Humans curate agent context by pinning nodes; agents read that curation through
|
|
|
44
44
|
human watches intent form and can veto mid-thought. Skip it only for trivial in-place tweaks or
|
|
45
45
|
high-frequency batch churn. The default TTL (~8s) expires between agent turns: signal with
|
|
46
46
|
`ttlMs: 30000` and settle by passing `intentId` on the mutation in the same or next call.
|
|
47
|
+
Since 0.4.5, an agent mutation WITHOUT a signal still shows a server-synthesized **auto-ghost**
|
|
48
|
+
(rendered lighter — dimmer, dotted, no veto — and settled instantly). That is a visibility
|
|
49
|
+
floor, not a replacement: only an explicit signal gives the human a real pre-mutation veto
|
|
50
|
+
window, your reasoning (`reason`), and staged multi-step previews. Batch and browser-human
|
|
51
|
+
actions never auto-ghost.
|
|
47
52
|
6. **Mutate through current composites.** Prefer the 16 composite MCP tools below.
|
|
48
53
|
7. **Arrange and validate.** After batch changes, use `canvas_view { action: "arrange" }` when
|
|
49
54
|
appropriate and always finish with `canvas_query { action: "validate" }`.
|
|
55
|
+
7b. **Show the human.** After creating user-facing output, bring the camera to it: a single node
|
|
56
|
+
gets `canvas_view { action: "focus", id }` (pans by default), a small cluster gets
|
|
57
|
+
`canvas_view { action: "fit", nodeIds: [...] }` with exactly the new ids. See
|
|
58
|
+
**In-View Placement & Sizing** below — auto-placement is board-relative, not camera-relative.
|
|
50
59
|
8. **Verify context pins.** Pin with `canvas_pin_nodes` or the browser's **Pin as context**, then
|
|
51
60
|
read `canvas://pinned-context`.
|
|
52
61
|
9. **Clean up temporary nodes.** Remove retry/test fixtures and restore the baseline snapshot when
|
|
@@ -62,6 +71,10 @@ pmx-canvas serve status
|
|
|
62
71
|
```
|
|
63
72
|
|
|
64
73
|
Both surfaces report `workspace`. It must match the intended workspace root.
|
|
74
|
+
For a full environment check in one command (health + workspace, CLI/server
|
|
75
|
+
version skew, MCP initialize handshake, temp-node create/search/remove
|
|
76
|
+
round-trip, board validation), run `pmx-canvas smoke` (0.4.6+) — JSON report,
|
|
77
|
+
exit 1 on failure.
|
|
65
78
|
|
|
66
79
|
- If `responsive: true` but `pidRunning: false`, treat the listener as potentially stale.
|
|
67
80
|
- On mismatch, do not mutate. Start the intended workspace on an explicit free port:
|
|
@@ -92,14 +105,17 @@ Both surfaces report `workspace`. It must match the intended workspace root.
|
|
|
92
105
|
| Persistent context cards | `context` via `canvas_node` |
|
|
93
106
|
| Event/check stream | `ledger` or `trace` via `canvas_node` |
|
|
94
107
|
| Local source with live updates | `file` via `canvas_node` |
|
|
108
|
+
| Code review / unified diff (0.4.6+) | `diff` via `canvas_node` (content = diff text; link to its file node with a `references` edge) |
|
|
109
|
+
| Flowchart / sequence / state diagram (0.4.6+) | `mermaid` via `canvas_node` (content = mermaid source; renders client-side, no hosted app) |
|
|
95
110
|
| Image | `image` via `canvas_node` |
|
|
96
111
|
| Cached URL content | `webpage` via `canvas_node` |
|
|
97
112
|
| Structured UI | `json-render` via `canvas_render` |
|
|
98
113
|
| Chart | `graph` via `canvas_render` |
|
|
114
|
+
| Live work-item board (0.4.6+) | `canvas_render { action: "workboard" }` — one board node, auto-refreshes on work-item changes |
|
|
99
115
|
| Generated communication surface | HTML primitive via `canvas_node` |
|
|
100
116
|
| Self-contained HTML/JS | `html` via `canvas_node` |
|
|
101
117
|
| Hosted interactive MCP app | `canvas_app { action: "open-mcp-app" }` |
|
|
102
|
-
| Excalidraw diagram | `canvas_app { action: "diagram" }` |
|
|
118
|
+
| Excalidraw diagram (interactive/human drawing) | `canvas_app { action: "diagram" }` — prefer `mermaid` for agent-authored diagrams |
|
|
103
119
|
| Bundled React artifact | `canvas_app { action: "build-artifact" }` |
|
|
104
120
|
|
|
105
121
|
Use the lightest tier that communicates the result. Do not build a web artifact when markdown,
|
|
@@ -159,6 +175,35 @@ v0.4.0 after their deprecated 0.3.x window.
|
|
|
159
175
|
- Edge types: `flow`, `depends-on`, `relation`, `references`.
|
|
160
176
|
- After manual or batch layout changes, run `canvas_query { action: "validate" }`.
|
|
161
177
|
|
|
178
|
+
## In-View Placement & Sizing (required for user-facing nodes)
|
|
179
|
+
|
|
180
|
+
Auto-placement (omitting `x`/`y`) is **board-relative, not camera-relative**: it places right of
|
|
181
|
+
the last node or scans rows from the origin, ignoring where the human is looking. On a board with
|
|
182
|
+
distant nodes, an auto-placed node lands off-camera.
|
|
183
|
+
|
|
184
|
+
1. Omit `x`/`y` only on an empty or locally dense board. Otherwise place near the human's
|
|
185
|
+
attention: the pinned/focused neighborhood, or explicit coordinates beside the last
|
|
186
|
+
user-facing output (gap ≥ 24–48 px).
|
|
187
|
+
2. After creating nodes the human should see, pan the camera (operating-sequence step 7b):
|
|
188
|
+
`focus` for one node, `fit` with exactly the new `nodeIds` for a cluster. Use `noPan`
|
|
189
|
+
(`focus --no-pan`) only when you must not steal the camera.
|
|
190
|
+
3. Never fit the whole board to "show" new work — on a board with outliers that miniaturizes
|
|
191
|
+
everything. Always pass explicit `nodeIds` to `fit`.
|
|
192
|
+
4. Never leave user-facing output at far coordinates without a focus/fit.
|
|
193
|
+
|
|
194
|
+
**Size for content.** Omitting `width`/`height` gives readable per-type defaults — prefer them:
|
|
195
|
+
markdown 640×420, status 360×200, file 520×360, diff 640×420, mermaid 640×460, html 720×640,
|
|
196
|
+
graph 760×520, mcp-app 960×600, web-artifact 960×720. Since 0.4.6 the server clamps explicit creation sizes UP to per-type
|
|
197
|
+
readability floors (e.g. markdown 360×180, graph/json-render/html 420×280, mcp-app 480×320) —
|
|
198
|
+
a tiny probe size silently becomes the floor. `strictSize: true` is the only opt-out (a fixed
|
|
199
|
+
scrolling frame you genuinely want small). `canvas_query { action: "validate" }` additionally
|
|
200
|
+
reports any node below its floor as an advisory `sizeWarnings` entry — treat a non-empty list
|
|
201
|
+
as layout work left to do.
|
|
202
|
+
|
|
203
|
+
**Token hygiene.** For routine state checks use `canvas_ax_state { action: "get" }` WITHOUT
|
|
204
|
+
`includeContext` — the full AX context payload is ~10× larger; request it only when you are
|
|
205
|
+
actually consuming context.
|
|
206
|
+
|
|
162
207
|
## Context Pins
|
|
163
208
|
|
|
164
209
|
Context pins are the primary human-to-agent bridge:
|
|
@@ -236,24 +281,25 @@ Prefer `canvas_query { action: "search" }` over parsing the full layout.
|
|
|
236
281
|
- Hosted MCP-app/ext-app nodes such as Excalidraw require the in-canvas host bridge and are not
|
|
237
282
|
standalone **Open as site** targets. URL-backed viewers and bundled web artifacts remain
|
|
238
283
|
openable.
|
|
239
|
-
- A
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
284
|
+
- A standalone html surface (`/api/canvas/surface/:id` opened as a site) is a VISUAL view: it
|
|
285
|
+
renders the same content and theme, but `window.PMX_AX` is not injected without the canvas
|
|
286
|
+
iframe's per-mount nonce, so AX buttons only work inside the in-canvas node (0.4.4 Codex note).
|
|
287
|
+
Do not tell a user a standalone tab's controls will steer the agent.
|
|
288
|
+
- A hosted ext-app (Excalidraw) node in a **WebKit** host panel (e.g. the GitHub Copilot app's
|
|
289
|
+
embedded WKWebView) historically could render as a black tile — a host compositor paint race
|
|
290
|
+
on the nested iframe, **not** a broken node (the session is healthy, `sessionStatus` is
|
|
291
|
+
`ready`, and it renders fine in Chrome/Codex). Since 0.4.6 the canvas runs a **paint oracle +
|
|
292
|
+
recovery ladder** under WebKit: present-at-load ext-apps mount strictly one at a time (the
|
|
293
|
+
cold burst was the trigger), each frame answers a double-rAF **paint probe** after settle, and
|
|
294
|
+
on silence the ladder escalates — soft-expand cycle (the automatic analogue of the proven
|
|
295
|
+
enlarge+close) → serialized remount → an explicit "App surface failed to paint / Retry"
|
|
296
|
+
affordance. The connecting overlay stays up until paint is confirmed, so a black layer is
|
|
297
|
+
never presented as ready. The recovery trail
|
|
298
|
+
(`GET /api/canvas/debug/ext-app-recovery` / `window.__PMX_EXTAPP_LOG`) now records
|
|
299
|
+
`mount-slot`, `paint-ok`, `paint-fail`, `soft-expand-cycle`, and `recovery-exhausted` — when
|
|
300
|
+
diagnosing, trust `paint-ok`/`paint-fail`, and never assert health from `settled` alone. If a
|
|
301
|
+
tile still shows the Retry affordance, click Retry (fresh recovery budget) or expand-then-close;
|
|
302
|
+
attach the recovery trail when reporting.
|
|
257
303
|
- Ext-app frame documents live in server memory. Through 0.4.0, killing/restarting the daemon
|
|
258
304
|
while a panel stays open leaves ext-app tiles on dead frame URLs (`Frame document not found`,
|
|
259
305
|
0.4.0 report Finding S) until a full workbench reload. Since 0.4.1 the browser revalidates its
|
|
@@ -272,11 +318,12 @@ Prefer `canvas_query { action: "search" }` over parsing the full layout.
|
|
|
272
318
|
iframe) can block child iframes from loading ANY `src` URL, breaking every iframe-backed node
|
|
273
319
|
with a gray placeholder. The canvas probes this at boot and auto-falls back to fetching
|
|
274
320
|
same-origin surfaces and rendering them inline via `srcdoc` (HTML, graph, json-render, frame
|
|
275
|
-
documents). In Amp orbs specifically, the server sees `AMP_ORB` in its environment and
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
321
|
+
documents). In Amp orbs specifically, the server sees `AMP_ORB` in its environment and adapts
|
|
322
|
+
end to end: it binds the portal-assigned `$PORT` automatically (no port flag in the service
|
|
323
|
+
command), stamps the page so the embedded client skips the (there-unreliable) probe, goes
|
|
324
|
+
straight to `srcdoc`, AND defaults to the polling transport (the portal proxy buffers SSE —
|
|
325
|
+
waiting out the watchdog could trip the boot modal); HTML surfaces also inline their theme
|
|
326
|
+
stylesheet so they render styled. External app
|
|
280
327
|
URLs cannot be inlined (cross-origin) and may stay blocked in such hosts. Force a mode with
|
|
281
328
|
`/workbench?iframe-mode=srcdoc` (or `iframe-mode=src`) when diagnosing.
|
|
282
329
|
- Graph and json-render standalone surfaces use `display=site` and fill the browser viewport, and
|