pmx-canvas 0.2.1 → 0.2.3
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 +119 -0
- package/Readme.md +2 -2
- package/dist/canvas/global.css +260 -0
- package/dist/canvas/index.js +76 -76
- package/dist/json-render/index.js +2 -2
- package/dist/types/client/canvas/IntentLayer.d.ts +1 -0
- package/dist/types/client/state/intent-bridge.d.ts +10 -0
- package/dist/types/client/state/intent-store.d.ts +25 -0
- package/dist/types/json-render/server.d.ts +1 -1
- package/dist/types/server/index.d.ts +34 -4
- package/dist/types/server/intent-registry.d.ts +45 -0
- package/dist/types/server/operations/ops/intent.d.ts +2 -0
- package/dist/types/shared/ax-intent.d.ts +58 -0
- package/docs/mcp.md +21 -2
- package/docs/screenshot.png +0 -0
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +200 -1305
- package/skills/pmx-canvas/evals/evals.json +255 -1
- package/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/jwt.ts +17 -0
- package/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/login.ts +12 -0
- package/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/middleware.ts +13 -0
- package/skills/pmx-canvas/evals/fixtures/code-exploration/src/routes/auth.ts +13 -0
- package/skills/pmx-canvas/evals/fixtures/investigation-board/src/handlers/users.ts +27 -0
- package/skills/pmx-canvas/references/full-reference.md +1445 -0
- package/src/cli/index.ts +21 -4
- package/src/client/canvas/CanvasNode.tsx +13 -13
- package/src/client/canvas/CanvasViewport.tsx +2 -0
- package/src/client/canvas/ContextMenu.tsx +25 -19
- package/src/client/canvas/IntentLayer.tsx +278 -0
- package/src/client/nodes/ExtAppFrame.tsx +32 -23
- package/src/client/state/intent-bridge.ts +31 -0
- package/src/client/state/intent-store.ts +107 -0
- package/src/client/state/sse-bridge.ts +31 -0
- package/src/client/theme/global.css +260 -0
- package/src/json-render/charts/components.tsx +18 -4
- package/src/json-render/renderer/index.tsx +11 -2
- package/src/json-render/server.ts +1 -1
- package/src/server/index.ts +240 -158
- package/src/server/intent-registry.ts +324 -0
- package/src/server/operations/composites.ts +11 -0
- package/src/server/operations/index.ts +2 -0
- package/src/server/operations/ops/edges.ts +1 -0
- package/src/server/operations/ops/groups.ts +3 -0
- package/src/server/operations/ops/intent.ts +132 -0
- package/src/server/operations/ops/json-render.ts +3 -0
- package/src/server/operations/ops/nodes.ts +3 -0
- package/src/server/operations/ops/webview.ts +15 -4
- package/src/server/operations/registry.ts +68 -3
- package/src/server/server.ts +40 -12
- package/src/shared/ax-intent.ts +64 -0
- package/src/shared/surface.ts +5 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function IntentLayer(): import("preact/src").JSX.Element | null;
|
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
export declare function sendIntent(type: string, payload?: Record<string, unknown>): Promise<{
|
|
3
3
|
ok: boolean;
|
|
4
4
|
}>;
|
|
5
|
+
/**
|
|
6
|
+
* Veto a forming ghost intent at the mutation gate, then queue steering for the
|
|
7
|
+
* active agent session only when the server accepted the veto.
|
|
8
|
+
*/
|
|
9
|
+
export declare function vetoGhostIntent(intent: {
|
|
10
|
+
id: string;
|
|
11
|
+
kind: string;
|
|
12
|
+
label?: string;
|
|
13
|
+
reason?: string;
|
|
14
|
+
}): Promise<boolean>;
|
|
5
15
|
/** Fetch rendered markdown HTML from the server. */
|
|
6
16
|
export declare function renderMarkdown(markdown: string): Promise<string>;
|
|
7
17
|
/** Fetch file content from the server. */
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PmxAxIntent } from '../../shared/ax-intent.js';
|
|
2
|
+
/**
|
|
3
|
+
* Client-side store for the Ghost Cursor of Intent. Ghosts are ephemeral
|
|
4
|
+
* presence pushed over SSE (`ax-intent` / `ax-intent-clear`); this store mirrors
|
|
5
|
+
* them into a signal the IntentLayer renders, tracks a short exit phase so
|
|
6
|
+
* settle/dissolve can animate, and prunes anything the server's TTL frame did
|
|
7
|
+
* not reach (SSE backstop). Nothing here is ever persisted.
|
|
8
|
+
*/
|
|
9
|
+
export type IntentPhase = 'forming' | 'settling' | 'dissolving';
|
|
10
|
+
export interface ClientIntent extends PmxAxIntent {
|
|
11
|
+
phase: IntentPhase;
|
|
12
|
+
/** The real node a settled intent became — seeds the settle morph. */
|
|
13
|
+
settledNodeId?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const intents: import("@preact/signals-core").Signal<Map<string, ClientIntent>>;
|
|
16
|
+
/** The ghost currently hovered — drives Esc-to-veto. */
|
|
17
|
+
export declare const hoveredIntentId: import("@preact/signals-core").Signal<string | null>;
|
|
18
|
+
/** A live `ax-intent` frame: (re)place the ghost in its forming state. */
|
|
19
|
+
export declare function upsertIntent(intent: PmxAxIntent): void;
|
|
20
|
+
export declare function removeIntent(id: string): void;
|
|
21
|
+
/** Resolve a ghost into a real node — the settle morph, then removal. */
|
|
22
|
+
export declare function settleIntent(id: string, settledNodeId?: string): void;
|
|
23
|
+
/** Dissolve a ghost (expired / vetoed / evicted / abandoned), then remove it. */
|
|
24
|
+
export declare function dissolveIntent(id: string): void;
|
|
25
|
+
export declare function resetIntents(): void;
|
|
@@ -88,7 +88,7 @@ export declare function buildJsonRenderViewerHtml(options: {
|
|
|
88
88
|
title: string;
|
|
89
89
|
spec: JsonRenderSpec;
|
|
90
90
|
theme?: 'dark' | 'light' | 'high-contrast';
|
|
91
|
-
display?: 'expanded';
|
|
91
|
+
display?: 'expanded' | 'site';
|
|
92
92
|
devtools?: boolean;
|
|
93
93
|
nodeId?: string;
|
|
94
94
|
axToken?: string;
|
|
@@ -2,6 +2,7 @@ import { EventEmitter } from 'node:events';
|
|
|
2
2
|
import { canvasState } from './canvas-state.js';
|
|
3
3
|
import type { CanvasAnnotation, CanvasNodeState, CanvasEdge, CanvasLayout } from './canvas-state.js';
|
|
4
4
|
import { type AxInteractionInput, type AxInteractionPublicResult } from './ax-interaction.js';
|
|
5
|
+
import type { PmxAxIntent } from '../shared/ax-intent.js';
|
|
5
6
|
import type { PmxAxActivityKind, PmxAxApprovalGate, PmxAxCommandDescriptor, PmxAxContext, PmxAxElicitation, PmxAxEvent, PmxAxEvidence, PmxAxEvidenceKind, PmxAxFocusState, PmxAxHostCapability, PmxAxMode, PmxAxModeRequest, PmxAxPolicy, PmxAxReviewAnchorType, PmxAxReviewAnnotation, PmxAxReviewKind, PmxAxReviewRegion, PmxAxReviewSeverity, PmxAxReviewStatus, PmxAxSource, PmxAxState, PmxAxSteeringMessage, PmxAxWorkItem, PmxAxWorkItemStatus } from './ax-state.js';
|
|
6
7
|
import type { AxTimelineQuery } from './canvas-db.js';
|
|
7
8
|
import { searchNodes } from './spatial-analysis.js';
|
|
@@ -30,6 +31,7 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
30
31
|
constructor(options?: {
|
|
31
32
|
port?: number;
|
|
32
33
|
});
|
|
34
|
+
private runIntentCommit;
|
|
33
35
|
start(options?: {
|
|
34
36
|
open?: boolean;
|
|
35
37
|
automationWebView?: boolean | CanvasAutomationWebViewOptions;
|
|
@@ -47,6 +49,7 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
47
49
|
* or keep the whole node — both work. (Previously returned a bare id string.)
|
|
48
50
|
*/
|
|
49
51
|
addNode(input: {
|
|
52
|
+
intentId?: string;
|
|
50
53
|
type: CanvasNodeState['type'];
|
|
51
54
|
title?: string;
|
|
52
55
|
content?: string;
|
|
@@ -67,6 +70,7 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
67
70
|
strictSize?: boolean;
|
|
68
71
|
}): SdkCanvasNode;
|
|
69
72
|
addWebpageNode(input: {
|
|
73
|
+
intentId?: string;
|
|
70
74
|
title?: string;
|
|
71
75
|
url: string;
|
|
72
76
|
x?: number;
|
|
@@ -90,8 +94,11 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
90
94
|
}>;
|
|
91
95
|
updateNode(id: string, patch: Partial<CanvasNodeState> & Record<string, unknown>): void;
|
|
92
96
|
/** Remove a node. Missing id throws (plan-005 unifies this across surfaces). */
|
|
93
|
-
removeNode(id: string
|
|
97
|
+
removeNode(id: string, options?: {
|
|
98
|
+
intentId?: string;
|
|
99
|
+
}): void;
|
|
94
100
|
addEdge(input: {
|
|
101
|
+
intentId?: string;
|
|
95
102
|
from?: string;
|
|
96
103
|
to?: string;
|
|
97
104
|
fromSearch?: string;
|
|
@@ -112,6 +119,7 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
112
119
|
* If childIds are provided, the group auto-sizes to contain them with padding.
|
|
113
120
|
*/
|
|
114
121
|
createGroup(input: {
|
|
122
|
+
intentId?: string;
|
|
115
123
|
title?: string;
|
|
116
124
|
childIds?: string[];
|
|
117
125
|
x?: number;
|
|
@@ -124,9 +132,12 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
124
132
|
/** Add nodes to an existing group. */
|
|
125
133
|
groupNodes(groupId: string, childIds: string[], options?: {
|
|
126
134
|
childLayout?: 'grid' | 'column' | 'flow';
|
|
135
|
+
intentId?: string;
|
|
127
136
|
}): boolean;
|
|
128
137
|
/** Remove all children from a group (the group node remains). */
|
|
129
|
-
ungroupNodes(groupId: string
|
|
138
|
+
ungroupNodes(groupId: string, options?: {
|
|
139
|
+
intentId?: string;
|
|
140
|
+
}): boolean;
|
|
130
141
|
clear(): void;
|
|
131
142
|
arrange(layout?: 'grid' | 'column' | 'flow'): void;
|
|
132
143
|
focusNode(id: string, options?: {
|
|
@@ -155,6 +166,18 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
155
166
|
source?: PmxAxSource;
|
|
156
167
|
}): PmxAxSteeringMessage;
|
|
157
168
|
markSteeringDelivered(id: string): boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Ghost Cursor of Intent — announce a spatial move before making it. The ghost
|
|
171
|
+
* is ephemeral presence (auto-expiring, never snapshotted); the registry emits
|
|
172
|
+
* the `ax-intent` SSE frame so the browser paints a pre-commit placeholder.
|
|
173
|
+
*/
|
|
174
|
+
signalIntent(input: Record<string, unknown>): PmxAxIntent;
|
|
175
|
+
updateIntent(id: string, patch: Record<string, unknown>): PmxAxIntent;
|
|
176
|
+
/** Dissolve a ghost; pass `settledNodeId` once the real node has landed. */
|
|
177
|
+
clearIntent(id: string, options?: {
|
|
178
|
+
settledNodeId?: string;
|
|
179
|
+
vetoed?: boolean;
|
|
180
|
+
}): boolean;
|
|
158
181
|
/** Undelivered steering for a consumer (loop-safe; excludes consumer-originated). */
|
|
159
182
|
getPendingSteering(options?: {
|
|
160
183
|
consumer?: string;
|
|
@@ -440,7 +463,9 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
440
463
|
timeoutMs?: number;
|
|
441
464
|
}): Promise<OpenMcpAppCoreResult>;
|
|
442
465
|
addDiagram(input: DiagramPresetOpenInput): Promise<OpenMcpAppCoreResult>;
|
|
443
|
-
addJsonRenderNode(input: JsonRenderNodeInput
|
|
466
|
+
addJsonRenderNode(input: JsonRenderNodeInput & {
|
|
467
|
+
intentId?: string;
|
|
468
|
+
}): {
|
|
444
469
|
id: string;
|
|
445
470
|
url: string;
|
|
446
471
|
spec: JsonRenderSpec;
|
|
@@ -452,6 +477,7 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
452
477
|
* reloads the viewer as the specVersion bumps.
|
|
453
478
|
*/
|
|
454
479
|
streamJsonRenderNode(input: {
|
|
480
|
+
intentId?: string;
|
|
455
481
|
nodeId?: string;
|
|
456
482
|
title?: string;
|
|
457
483
|
patches?: unknown[];
|
|
@@ -471,6 +497,7 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
471
497
|
streamStatus: 'open' | 'closed';
|
|
472
498
|
};
|
|
473
499
|
addHtmlNode(input: {
|
|
500
|
+
intentId?: string;
|
|
474
501
|
html: string;
|
|
475
502
|
title?: string;
|
|
476
503
|
summary?: string;
|
|
@@ -493,6 +520,7 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
493
520
|
};
|
|
494
521
|
}): SdkCanvasNode;
|
|
495
522
|
addHtmlPrimitive(input: {
|
|
523
|
+
intentId?: string;
|
|
496
524
|
kind: HtmlPrimitiveKind;
|
|
497
525
|
title?: string;
|
|
498
526
|
data?: Record<string, unknown>;
|
|
@@ -507,7 +535,9 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
507
535
|
title: string;
|
|
508
536
|
htmlBytes: number;
|
|
509
537
|
};
|
|
510
|
-
addGraphNode(input: GraphNodeInput
|
|
538
|
+
addGraphNode(input: GraphNodeInput & {
|
|
539
|
+
intentId?: string;
|
|
540
|
+
}): {
|
|
511
541
|
id: string;
|
|
512
542
|
url: string;
|
|
513
543
|
spec: JsonRenderSpec;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type PmxAxIntent, type PmxAxIntentKind } from '../shared/ax-intent.js';
|
|
2
|
+
type IntentEmitter = (event: string, payload: Record<string, unknown>) => void;
|
|
3
|
+
export declare class IntentRegistry {
|
|
4
|
+
private readonly intents;
|
|
5
|
+
private readonly vetoedIntentIds;
|
|
6
|
+
private readonly committingIntentIds;
|
|
7
|
+
private emit;
|
|
8
|
+
private sweepTimer;
|
|
9
|
+
/** Inject the workbench SSE emitter (server.ts wires this at module load). */
|
|
10
|
+
setEmitter(emitter: IntentEmitter | null): void;
|
|
11
|
+
list(): PmxAxIntent[];
|
|
12
|
+
/** Signal a new (or replace an existing) intent. Returns the stored envelope. */
|
|
13
|
+
signal(raw: unknown): PmxAxIntent;
|
|
14
|
+
/** Patch a live intent (position/label/reason/confidence/seq) and bump its TTL. */
|
|
15
|
+
update(id: string, raw: unknown): PmxAxIntent;
|
|
16
|
+
/**
|
|
17
|
+
* Clear an intent. `settledNodeId` resolves it INTO a real node (the settle
|
|
18
|
+
* morph); `vetoed` marks a human pre-emptive veto. Either way the ghost
|
|
19
|
+
* dissolves. Returns true when an intent was actually removed.
|
|
20
|
+
*/
|
|
21
|
+
clear(id: string, opts?: {
|
|
22
|
+
settledNodeId?: string;
|
|
23
|
+
vetoed?: boolean;
|
|
24
|
+
}): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Gate one real mutation behind a live, non-vetoed intent. The claim is
|
|
27
|
+
* synchronous: once this method has accepted the intent, a later veto cannot
|
|
28
|
+
* race in between the check and the mutation.
|
|
29
|
+
*/
|
|
30
|
+
beginCommit(id: string, allowedKinds: readonly PmxAxIntentKind[]): PmxAxIntent;
|
|
31
|
+
completeCommit(id: string, settledNodeId?: string): void;
|
|
32
|
+
abortCommit(id: string): void;
|
|
33
|
+
runCommit<T>(id: string, allowedKinds: readonly PmxAxIntentKind[], mutate: () => T | Promise<T>, settledNodeId: (result: T, intent: PmxAxIntent) => string | undefined): Promise<T>;
|
|
34
|
+
/** Drop every live intent without per-id SSE (used on hard resets). */
|
|
35
|
+
reset(): void;
|
|
36
|
+
private rememberVeto;
|
|
37
|
+
private pruneVetoTombstones;
|
|
38
|
+
private evictOverflow;
|
|
39
|
+
private sweep;
|
|
40
|
+
private ensureSweeper;
|
|
41
|
+
private maybeStopSweeper;
|
|
42
|
+
}
|
|
43
|
+
/** Process-wide singleton, shared across HTTP handlers, MCP ops, and the SDK. */
|
|
44
|
+
export declare const intentRegistry: IntentRegistry;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ghost Cursor of Intent — the shared pre-commit "intent" envelope.
|
|
3
|
+
*
|
|
4
|
+
* An intent is EPHEMERAL PRESENCE, not canvas state: it describes a move the
|
|
5
|
+
* agent is ABOUT to make (create / move / connect / remove / edit) so the canvas
|
|
6
|
+
* can paint a faint placeholder before the real mutation lands. Like a
|
|
7
|
+
* multiplayer cursor, it auto-expires, is count-capped, and never enters
|
|
8
|
+
* `canvas_get_layout`, `state.json`, or snapshots.
|
|
9
|
+
*
|
|
10
|
+
* This module is import-shared by the server (IntentRegistry + the intent ops)
|
|
11
|
+
* and the client (intent-store + IntentLayer); it must stay free of any
|
|
12
|
+
* server-only or client-only imports.
|
|
13
|
+
*/
|
|
14
|
+
export type PmxAxIntentKind = 'create' | 'move' | 'connect' | 'remove' | 'edit';
|
|
15
|
+
export type PmxAxIntentEdgeType = 'relation' | 'depends-on' | 'flow' | 'references';
|
|
16
|
+
export interface PmxAxIntentEdge {
|
|
17
|
+
from: string;
|
|
18
|
+
to: string;
|
|
19
|
+
type: PmxAxIntentEdgeType;
|
|
20
|
+
}
|
|
21
|
+
export interface PmxAxIntent {
|
|
22
|
+
/** Stable id → update / clear / veto. Auto-generated when a signal omits it. */
|
|
23
|
+
id: string;
|
|
24
|
+
kind: PmxAxIntentKind;
|
|
25
|
+
/** create: where the new node forms. move: the destination. */
|
|
26
|
+
position?: {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
};
|
|
30
|
+
/** move / edit / remove: the existing node the intent targets. */
|
|
31
|
+
nodeId?: string;
|
|
32
|
+
/** connect: the edge about to be drawn. */
|
|
33
|
+
edge?: PmxAxIntentEdge;
|
|
34
|
+
/** Node type the ghost renders (icon + type badge). Defaults to a neutral box. */
|
|
35
|
+
nodeType?: string;
|
|
36
|
+
/** Short action label shown on the ghost chip ("Add evidence"). */
|
|
37
|
+
label?: string;
|
|
38
|
+
/** WHY — shown beneath the ghost. The legibility payoff. */
|
|
39
|
+
reason?: string;
|
|
40
|
+
/** 0..1 → ghost opacity/solidity. */
|
|
41
|
+
confidence?: number;
|
|
42
|
+
/** Ordering hint for staged-batch ghosts (the numbered previsualization rail). */
|
|
43
|
+
seq?: number;
|
|
44
|
+
/** Source label of the surface that signalled the intent (mcp/api/sdk/...). */
|
|
45
|
+
source?: string;
|
|
46
|
+
/** Epoch ms when the intent was first signalled. */
|
|
47
|
+
createdAt: number;
|
|
48
|
+
/** Epoch ms when the intent auto-dissolves if not settled/cleared first. */
|
|
49
|
+
expiresAt: number;
|
|
50
|
+
}
|
|
51
|
+
export declare const INTENT_KINDS: PmxAxIntentKind[];
|
|
52
|
+
export declare const INTENT_EDGE_TYPES: PmxAxIntentEdgeType[];
|
|
53
|
+
/** Default lifetime of an unsettled ghost. */
|
|
54
|
+
export declare const DEFAULT_INTENT_TTL_MS = 8000;
|
|
55
|
+
/** Hard ceiling on TTL so a stuck ghost can never linger. */
|
|
56
|
+
export declare const MAX_INTENT_TTL_MS = 60000;
|
|
57
|
+
/** Live-intent cap — oldest is evicted past this (presence, not a queue). */
|
|
58
|
+
export declare const MAX_LIVE_INTENTS = 12;
|
package/docs/mcp.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# MCP reference
|
|
2
2
|
|
|
3
|
-
PMX Canvas ships an MCP stdio server with **
|
|
3
|
+
PMX Canvas ships an MCP stdio server with **84 tools** + **14 core resources**,
|
|
4
4
|
plus per-skill resources at `canvas://skills/<name>`. The server emits
|
|
5
5
|
`notifications/resources/updated` when canvas state changes — humans pin
|
|
6
6
|
nodes in the browser, agents are notified immediately.
|
|
7
7
|
|
|
8
|
-
> **Consolidation in progress (plan-006/008).** The
|
|
8
|
+
> **Consolidation in progress (plan-006/008).** The 84 tools are 15 action-discriminated
|
|
9
9
|
> **composites** (recommended — see below) plus 69 legacy single-purpose tools.
|
|
10
10
|
> The composites fold the legacy tools behind an `action` (and, for `canvas_ax_gate`,
|
|
11
11
|
> a `kind`) enum; each action dispatches to the same operation, so behavior is
|
|
@@ -51,6 +51,25 @@ its `action` to the same operation the legacy tool used, so results are identica
|
|
|
51
51
|
| `canvas_ax_gate` | `request` · `resolve` · `await` × kind `approval` \| `elicitation` \| `mode` | `canvas_request_approval`, `canvas_resolve_approval`, `canvas_await_approval`, `canvas_request_elicitation`, `canvas_respond_elicitation`, `canvas_await_elicitation`, `canvas_request_mode`, `canvas_resolve_mode`, `canvas_await_mode` (9 → 1) |
|
|
52
52
|
| `canvas_ax_timeline` | `read` · `record-event` · `add-evidence` · `send-steering` | `canvas_get_ax_timeline`, `canvas_record_ax_event`, `canvas_add_evidence`, `canvas_send_steering` |
|
|
53
53
|
| `canvas_ax_delivery` | `claim` · `mark` | `canvas_claim_ax_delivery`, `canvas_mark_ax_delivery` |
|
|
54
|
+
| `canvas_intent` | `signal` · `update` · `clear` | _(new — Ghost Cursor of Intent; no legacy standalone tool)_ |
|
|
55
|
+
|
|
56
|
+
### `canvas_intent` — Ghost Cursor of Intent
|
|
57
|
+
|
|
58
|
+
Announce the spatial move you are **about** to make so the canvas paints a faint
|
|
59
|
+
pre-commit placeholder (a "ghost"). The human sees the next move forming — and can
|
|
60
|
+
veto it — before the mutation lands.
|
|
61
|
+
|
|
62
|
+
- `signal` — register an intent: `kind` (`create` \| `move` \| `connect` \| `remove` \| `edit`) plus the anchor it renders against (`position` for create/move, `nodeId` for move/edit/remove, `edge` for connect). Optional `label`, `reason`, `confidence` (0..1 → ghost opacity), `seq` (staged-batch ordering), `ttlMs` (default ~8s), and a stable `id` to update/clear later.
|
|
63
|
+
- `update` — patch a live intent by `id` (position/label/reason/confidence/ttlMs).
|
|
64
|
+
- `clear` — abandon/dissolve it explicitly. Normal linked mutations settle automatically.
|
|
65
|
+
|
|
66
|
+
Intents are **ephemeral presence**: never persisted, never snapshotted, never in
|
|
67
|
+
`canvas_get_layout`, and auto-expiring. They ride their own SSE channel
|
|
68
|
+
(`ax-intent` / `ax-intent-clear`) and replay to reconnecting browsers while still
|
|
69
|
+
live. Best practice — narrate your next move: `signal` → mutate with the returned
|
|
70
|
+
`intent.id` as `intentId`. A vetoed or expired linked mutation is rejected, and a
|
|
71
|
+
successful mutation settles the ghost automatically. Also reachable over HTTP:
|
|
72
|
+
`POST/PATCH/DELETE /api/canvas/ax/intent[/:id]`.
|
|
54
73
|
|
|
55
74
|
Field names match the underlying operation (e.g. `canvas_view { action: "focus", id }`,
|
|
56
75
|
`canvas_group { action: "create", childIds }`). `canvas_ax_gate` has two discriminators:
|
package/docs/screenshot.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmx-canvas",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
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",
|