pmx-canvas 0.2.0 → 0.2.2
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 +124 -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/ax-state-manager.d.ts +11 -0
- package/dist/types/server/ax-state.d.ts +2 -0
- package/dist/types/server/canvas-db.d.ts +13 -0
- package/dist/types/server/canvas-state.d.ts +5 -0
- 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/ax-host-adapter-contract.md +19 -1
- package/docs/http-api.md +4 -0
- package/docs/mcp.md +22 -3
- package/docs/screenshot.png +0 -0
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +197 -1283
- package/skills/pmx-canvas/evals/evals.json +199 -0
- package/skills/pmx-canvas/references/ax-html-control-surface.md +93 -0
- package/skills/pmx-canvas/references/full-reference.md +1441 -0
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +23 -7
- 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 +31 -22
- 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/ax-context.ts +8 -1
- package/src/server/ax-state-manager.ts +18 -0
- package/src/server/ax-state.ts +8 -0
- package/src/server/canvas-db.ts +35 -0
- package/src/server/canvas-state.ts +8 -0
- 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/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;
|
|
@@ -245,6 +245,17 @@ export declare class AxStateManager {
|
|
|
245
245
|
consumer?: string;
|
|
246
246
|
limit?: number;
|
|
247
247
|
}): PmxAxSteeringMessage[];
|
|
248
|
+
/**
|
|
249
|
+
* NEWEST undelivered steering first, for the compact AX context lead block (report
|
|
250
|
+
* #57) — so a fresh steer is visible even behind a long backlog. Loop-safe like
|
|
251
|
+
* getPendingSteering, but ordered DESC instead of the FIFO ASC delivery queue.
|
|
252
|
+
*/
|
|
253
|
+
getPendingSteeringForContext(options?: {
|
|
254
|
+
consumer?: string;
|
|
255
|
+
limit?: number;
|
|
256
|
+
}): PmxAxSteeringMessage[];
|
|
257
|
+
/** Total undelivered steering for a consumer (loop-safe), for the context backlog counts. */
|
|
258
|
+
getPendingSteeringCount(consumer?: string): number;
|
|
248
259
|
getAxTimelineSummary(): PmxAxTimelineSummary;
|
|
249
260
|
getAxTimeline(q?: AxTimelineQuery): {
|
|
250
261
|
events: PmxAxEvent[];
|
|
@@ -145,6 +145,8 @@ export interface PendingAxActivityItem {
|
|
|
145
145
|
}
|
|
146
146
|
export interface PmxAxDeliveryContext {
|
|
147
147
|
pendingSteering: PmxAxSteeringMessage[];
|
|
148
|
+
totalPending: number;
|
|
149
|
+
omittedPending: number;
|
|
148
150
|
pendingActivity: PendingAxActivityItem[];
|
|
149
151
|
}
|
|
150
152
|
export interface PmxAxContext {
|
|
@@ -53,6 +53,19 @@ export declare function loadPendingAxSteeringFromDB(db: Database, options?: {
|
|
|
53
53
|
consumer?: string;
|
|
54
54
|
limit?: number;
|
|
55
55
|
}): PmxAxSteeringMessage[];
|
|
56
|
+
/**
|
|
57
|
+
* NEWEST undelivered steering first (report #57) for the compact AX context lead
|
|
58
|
+
* block — so a fresh steer is visible even behind a long backlog. Loop-safe: excludes
|
|
59
|
+
* the consumer's own steering in SQL so the LIMIT applies after loop-prevention.
|
|
60
|
+
* Distinct from loadPendingAxSteeringFromDB (FIFO oldest-first) which the claim/ack
|
|
61
|
+
* delivery queue uses for ordered processing.
|
|
62
|
+
*/
|
|
63
|
+
export declare function loadNewestPendingAxSteeringFromDB(db: Database, options?: {
|
|
64
|
+
consumer?: string;
|
|
65
|
+
limit?: number;
|
|
66
|
+
}): PmxAxSteeringMessage[];
|
|
67
|
+
/** Total undelivered steering for a consumer (loop-safe — excludes the consumer's own). */
|
|
68
|
+
export declare function countPendingAxSteeringFromDB(db: Database, consumer?: string): number;
|
|
56
69
|
export declare function loadAxTimelineSummaryFromDB(db: Database): PmxAxTimelineSummary;
|
|
57
70
|
export declare function upsertAxHostCapabilityToDB(db: Database, cap: PmxAxHostCapability): void;
|
|
58
71
|
export declare function loadAxHostCapabilityFromDB(db: Database): PmxAxHostCapability | null;
|
|
@@ -431,6 +431,11 @@ declare class CanvasStateManager {
|
|
|
431
431
|
consumer?: string;
|
|
432
432
|
limit?: number;
|
|
433
433
|
}): PmxAxSteeringMessage[];
|
|
434
|
+
getPendingSteeringForContext(options?: {
|
|
435
|
+
consumer?: string;
|
|
436
|
+
limit?: number;
|
|
437
|
+
}): PmxAxSteeringMessage[];
|
|
438
|
+
getPendingSteeringCount(consumer?: string): number;
|
|
434
439
|
getAxTimelineSummary(): PmxAxTimelineSummary;
|
|
435
440
|
getAxTimeline(q?: AxTimelineQuery): {
|
|
436
441
|
events: PmxAxEvent[];
|
|
@@ -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;
|
|
@@ -42,7 +42,25 @@ message; it does **not** wake the agent. It reaches the next turn only when:
|
|
|
42
42
|
|
|
43
43
|
The `delivery` lead block (`GET /api/canvas/ax/context?consumer=<id>`) is the
|
|
44
44
|
robustness hedge: it's compact and sits above the full dump, so an adapter can inject
|
|
45
|
-
it un-truncated even on a busy board where the full context is clipped.
|
|
45
|
+
it un-truncated even on a busy board where the full context is clipped. Its
|
|
46
|
+
`pendingSteering` is **newest-first** (most recent at index 0), capped at 10, so a
|
|
47
|
+
*fresh* steer is always visible even behind a long backlog of old unacked steers
|
|
48
|
+
(report #57); `delivery.totalPending` / `delivery.omittedPending` tell the agent how
|
|
49
|
+
many more are queued so it can drain the FIFO `…/delivery/pending` endpoint when the
|
|
50
|
+
count is non-zero. **Adapters should read `delivery.pendingSteering`** (this compact,
|
|
51
|
+
count-bearing block), not `timeline.pendingSteering`.
|
|
52
|
+
|
|
53
|
+
### Canvas-origin steering does not wake the agent by itself (#59)
|
|
54
|
+
|
|
55
|
+
Recording a browser-origin `ax.steer` (and the `ok:true` ack a surface button gets —
|
|
56
|
+
report #55) means the steer is **queued on the timeline**, not delivered into a live
|
|
57
|
+
agent turn. PMX deliberately does not import a host SDK, so the *wake* — turning a
|
|
58
|
+
queued steer into a visible turn — is **adapter-owned**: a cooperating host adapter
|
|
59
|
+
must drain `…/delivery/pending?consumer=<id>` and call its native send (e.g.
|
|
60
|
+
`copilotSession.send`), then `…/delivery/<id>/mark` it. Until an adapter wires that,
|
|
61
|
+
canvas-origin steering is delivered on the next human turn, not pushed. A steering
|
|
62
|
+
surface should therefore label its button honestly ("queued for the agent's next
|
|
63
|
+
turn"), never imply it interrupts the agent now.
|
|
46
64
|
|
|
47
65
|
## The two primitives that close the loop
|
|
48
66
|
|
package/docs/http-api.md
CHANGED
|
@@ -246,6 +246,10 @@ curl "http://localhost:4313/api/canvas/ax/mode/<id>?waitMs=30000"
|
|
|
246
246
|
|
|
247
247
|
# Context — optional ?consumer= filters the compact, loop-safe `delivery` lead block
|
|
248
248
|
# (undelivered steering + open work/approvals it can act on) for per-turn injection.
|
|
249
|
+
# `delivery.pendingSteering` is NEWEST-first (most recent first), capped at 10, so a
|
|
250
|
+
# fresh steer is visible even behind a backlog; `delivery.totalPending` /
|
|
251
|
+
# `delivery.omittedPending` report how many more are queued. Drain the full FIFO
|
|
252
|
+
# (oldest-first) backlog via /api/canvas/ax/delivery/pending when omittedPending > 0.
|
|
249
253
|
curl "http://localhost:4313/api/canvas/ax/context?consumer=copilot"
|
|
250
254
|
|
|
251
255
|
# Commands — list the registry, invoke a command (records a `command` agent-event)
|
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:
|
|
@@ -157,7 +176,7 @@ Individual bundled skills are also readable at `canvas://skills/<name>`.
|
|
|
157
176
|
|----------|-------------|
|
|
158
177
|
| `canvas://pinned-context` | Content of pinned nodes + nearby unpinned neighbors |
|
|
159
178
|
| `canvas://ax` | PMX AX state: focus, work items, approval gates, review annotations |
|
|
160
|
-
| `canvas://ax-context` | Agent-readable pinned and focused AX context, plus timeline summary and host capability |
|
|
179
|
+
| `canvas://ax-context` | Agent-readable pinned and focused AX context, plus a compact `delivery` lead block (`pendingSteering` newest-first + `totalPending`/`omittedPending` counts), timeline summary, and host capability |
|
|
161
180
|
| `canvas://ax-work` | Canvas-bound AX work: work items, approval gates, review annotations, elicitations, mode requests, and tool/prompt policy |
|
|
162
181
|
| `canvas://ax-timeline` | Bounded AX timeline: recent agent-events, evidence, and steering messages |
|
|
163
182
|
| `canvas://ax-pending-steering` | Undelivered steering an adapterless MCP client can claim, act on, and mark delivered |
|
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.2",
|
|
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",
|