pmx-canvas 0.1.29 → 0.1.30

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +161 -0
  2. package/Readme.md +20 -10
  3. package/dist/canvas/global.css +13 -0
  4. package/dist/canvas/index.js +80 -163
  5. package/dist/canvas/surface-theme.css +142 -0
  6. package/dist/json-render/index.js +103 -103
  7. package/dist/types/client/nodes/HtmlNode.d.ts +0 -7
  8. package/dist/types/client/nodes/ax-node-actions.d.ts +18 -0
  9. package/dist/types/client/nodes/surface-url.d.ts +22 -0
  10. package/dist/types/client/state/attention-bridge.d.ts +3 -0
  11. package/dist/types/client/state/intent-bridge.d.ts +17 -0
  12. package/dist/types/json-render/renderer/index.d.ts +2 -0
  13. package/dist/types/json-render/schema.d.ts +2 -0
  14. package/dist/types/json-render/server.d.ts +2 -0
  15. package/dist/types/mcp/canvas-access.d.ts +47 -0
  16. package/dist/types/server/ax-interaction.d.ts +210 -0
  17. package/dist/types/server/ax-state.d.ts +67 -1
  18. package/dist/types/server/canvas-db.d.ts +4 -0
  19. package/dist/types/server/canvas-serialization.d.ts +2 -0
  20. package/dist/types/server/canvas-state.d.ts +47 -2
  21. package/dist/types/server/html-surface.d.ts +40 -0
  22. package/dist/types/server/index.d.ts +50 -2
  23. package/dist/types/server/mutation-history.d.ts +1 -1
  24. package/dist/types/server/placement.d.ts +1 -1
  25. package/dist/types/shared/surface.d.ts +19 -0
  26. package/docs/cli.md +30 -0
  27. package/docs/http-api.md +55 -0
  28. package/docs/mcp.md +40 -2
  29. package/docs/node-types.md +26 -0
  30. package/docs/plans/plan-004-pmx-ax-primitives.md +623 -394
  31. package/docs/sdk.md +20 -0
  32. package/package.json +2 -2
  33. package/skills/pmx-canvas/SKILL.md +107 -9
  34. package/src/cli/agent.ts +177 -0
  35. package/src/client/canvas/CanvasNode.tsx +8 -4
  36. package/src/client/canvas/ExpandedNodeOverlay.tsx +12 -0
  37. package/src/client/nodes/ContextNode.tsx +17 -0
  38. package/src/client/nodes/ExtAppFrame.tsx +40 -3
  39. package/src/client/nodes/FileNode.tsx +26 -0
  40. package/src/client/nodes/HtmlNode.tsx +60 -188
  41. package/src/client/nodes/McpAppNode.tsx +47 -2
  42. package/src/client/nodes/StatusNode.tsx +20 -0
  43. package/src/client/nodes/ax-node-actions.ts +39 -0
  44. package/src/client/nodes/surface-url.ts +48 -0
  45. package/src/client/state/attention-bridge.ts +5 -0
  46. package/src/client/state/intent-bridge.ts +33 -0
  47. package/src/client/theme/global.css +13 -0
  48. package/src/client/theme/surface-theme.css +142 -0
  49. package/src/json-render/renderer/index.tsx +31 -0
  50. package/src/json-render/schema.ts +4 -0
  51. package/src/json-render/server.ts +13 -0
  52. package/src/mcp/canvas-access.ts +195 -0
  53. package/src/mcp/server.ts +232 -2
  54. package/src/server/ax-context.ts +3 -0
  55. package/src/server/ax-interaction.ts +549 -0
  56. package/src/server/ax-state.ts +188 -2
  57. package/src/server/canvas-db.ts +20 -0
  58. package/src/server/canvas-operations.ts +11 -0
  59. package/src/server/canvas-serialization.ts +9 -0
  60. package/src/server/canvas-state.ts +177 -16
  61. package/src/server/html-surface.ts +170 -0
  62. package/src/server/index.ts +98 -0
  63. package/src/server/mutation-history.ts +5 -0
  64. package/src/server/placement.ts +5 -1
  65. package/src/server/server.ts +305 -0
  66. package/src/shared/surface.ts +38 -0
@@ -1,11 +1,4 @@
1
1
  import type { CanvasNodeState } from '../types';
2
- export declare function createHtmlNodeSrcDocForTest(userHtml: string, options: {
3
- theme: string;
4
- themeCss: string;
5
- themeToken?: string;
6
- presentation?: boolean;
7
- presentationExitToken?: string;
8
- }): string;
9
2
  export declare function shouldShowPresentationControls(node: CanvasNodeState): boolean;
10
3
  export declare function HtmlNode({ node, expanded, presentation, presentationExitToken, autoFocus, }: {
11
4
  node: CanvasNodeState;
@@ -0,0 +1,18 @@
1
+ import type { CanvasNodeState } from '../types';
2
+ /**
3
+ * Submit a native-node AX interaction (plan-004 Phase 2) and surface the outcome
4
+ * as a transient toast. Inline node controls call this; the server enforces the
5
+ * node's capabilities, so a denied interaction simply shows an error toast.
6
+ */
7
+ export declare function runNodeAxInteraction(node: CanvasNodeState, type: string, payload: Record<string, unknown> | undefined, successTitle: string): Promise<void>;
8
+ /** Shared style for the small inline AX action button on native nodes. */
9
+ export declare const axNodeActionButtonStyle: {
10
+ readonly padding: "3px 8px";
11
+ readonly fontSize: "10px";
12
+ readonly background: "var(--c-accent-12)";
13
+ readonly border: "1px solid var(--c-accent-25)";
14
+ readonly borderRadius: "4px";
15
+ readonly color: "var(--c-text-soft)";
16
+ readonly cursor: "pointer";
17
+ readonly flexShrink: 0;
18
+ };
@@ -0,0 +1,22 @@
1
+ import type { CanvasNodeState } from '../types';
2
+ /**
3
+ * Stable content hash (djb2) used to cache-bust the surface iframe `src` when a
4
+ * node's HTML changes. The server always serves current state, but a same `src`
5
+ * string won't reload the iframe on its own — bumping `?v=` does.
6
+ */
7
+ export declare function surfaceContentHash(input: string): string;
8
+ export interface SurfaceUrlOptions {
9
+ theme?: string;
10
+ themeToken?: string;
11
+ present?: boolean;
12
+ presentToken?: string;
13
+ v?: string;
14
+ /** Nonce authorizing iframe → parent AX emits (html bridge). */
15
+ axToken?: string;
16
+ }
17
+ /** Build the stable per-node surface URL (/api/canvas/surface/:id) the iframe and "Open as site" both use. */
18
+ export declare function nodeSurfaceUrl(nodeId: string, opts?: SurfaceUrlOptions): string;
19
+ /** Whether a node can be opened as a standalone site (shared with the server). */
20
+ export declare function canOpenAsSite(node: CanvasNodeState): boolean;
21
+ /** Open the node's surface in a new browser tab. */
22
+ export declare function openNodeAsSite(node: CanvasNodeState): void;
@@ -1,3 +1,6 @@
1
1
  import { type SseMessage } from '../../shared/semantic-attention.js';
2
+ import { type AttentionTone } from './attention-store';
3
+ /** Show a transient toast from arbitrary client code (e.g. AX action feedback). */
4
+ export declare function showToast(tone: AttentionTone, title: string, detail?: string, nodeIds?: string[]): void;
2
5
  export declare function resetAttentionBridge(): void;
3
6
  export declare function syncAttentionFromSse(message: SseMessage): void;
@@ -111,6 +111,23 @@ export declare function removeNodeFromClient(id: string): Promise<{
111
111
  ok: boolean;
112
112
  removed?: string;
113
113
  }>;
114
+ export interface AxInteractionRequest {
115
+ type: string;
116
+ sourceNodeId: string;
117
+ payload?: Record<string, unknown>;
118
+ sourceSurface?: 'native-node' | 'json-render' | 'html-node' | 'mcp-app' | 'adapter';
119
+ }
120
+ export interface AxInteractionResponse {
121
+ ok: boolean;
122
+ type?: string;
123
+ sourceNodeId?: string;
124
+ primitive?: unknown;
125
+ status?: number;
126
+ code?: string;
127
+ error?: string;
128
+ }
129
+ /** Submit a capability-gated AX interaction from a native node control. */
130
+ export declare function submitAxInteractionFromClient(input: AxInteractionRequest): Promise<AxInteractionResponse>;
114
131
  /** Commit the current viewport to the authoritative server state. */
115
132
  export declare function updateViewportFromClient(viewport: {
116
133
  x: number;
@@ -14,5 +14,7 @@ declare global {
14
14
  __PMX_CANVAS_JSON_RENDER_THEME__?: string;
15
15
  __PMX_CANVAS_JSON_RENDER_DISPLAY__?: string;
16
16
  __PMX_CANVAS_JSON_RENDER_DEVTOOLS__?: boolean;
17
+ __PMX_CANVAS_JSON_RENDER_NODE_ID__?: string;
18
+ __PMX_CANVAS_AX_TOKEN__?: string;
17
19
  }
18
20
  }
@@ -6,6 +6,7 @@ export declare const schema: import("@json-render/core").Schema<{
6
6
  props: import("@json-render/core").SchemaType<"propsOf", string>;
7
7
  children: import("@json-render/core").SchemaType<"array", import("@json-render/core").SchemaType<"string", unknown>>;
8
8
  visible: import("@json-render/core").SchemaType<"any", unknown>;
9
+ on: import("@json-render/core").SchemaType<"any", unknown>;
9
10
  }>>;
10
11
  }>;
11
12
  catalog: import("@json-render/core").SchemaType<"object", {
@@ -29,6 +30,7 @@ export declare const elementTreeSchema: import("@json-render/core").Schema<{
29
30
  props: import("@json-render/core").SchemaType<"propsOf", string>;
30
31
  children: import("@json-render/core").SchemaType<"array", import("@json-render/core").SchemaType<"string", unknown>>;
31
32
  visible: import("@json-render/core").SchemaType<"any", unknown>;
33
+ on: import("@json-render/core").SchemaType<"any", unknown>;
32
34
  }>>;
33
35
  }>;
34
36
  catalog: import("@json-render/core").SchemaType<"object", {
@@ -90,4 +90,6 @@ export declare function buildJsonRenderViewerHtml(options: {
90
90
  theme?: 'dark' | 'light' | 'high-contrast';
91
91
  display?: 'expanded';
92
92
  devtools?: boolean;
93
+ nodeId?: string;
94
+ axToken?: string;
93
95
  }): Promise<string>;
@@ -29,6 +29,22 @@ type SetAxFocusResult = ReturnType<PmxCanvas['setAxFocus']>;
29
29
  type RecordAxEventInput = Parameters<PmxCanvas['recordAxEvent']>[0];
30
30
  type RecordAxEventResult = ReturnType<PmxCanvas['recordAxEvent']>;
31
31
  type SendSteeringResult = ReturnType<PmxCanvas['sendSteering']>;
32
+ type SubmitAxInteractionInput = Parameters<PmxCanvas['submitAxInteraction']>[0];
33
+ type SubmitAxInteractionResult = ReturnType<PmxCanvas['submitAxInteraction']>;
34
+ type GetPendingSteeringResult = ReturnType<PmxCanvas['getPendingSteering']>;
35
+ type ListElicitationsResult = ReturnType<PmxCanvas['listElicitations']>;
36
+ type RequestElicitationInput = Parameters<PmxCanvas['requestElicitation']>[0];
37
+ type RequestElicitationResult = ReturnType<PmxCanvas['requestElicitation']>;
38
+ type RespondElicitationResult = ReturnType<PmxCanvas['respondElicitation']>;
39
+ type ListModeRequestsResult = ReturnType<PmxCanvas['listModeRequests']>;
40
+ type RequestModeInput = Parameters<PmxCanvas['requestMode']>[0];
41
+ type RequestModeResult = ReturnType<PmxCanvas['requestMode']>;
42
+ type ResolveModeRequestResult = ReturnType<PmxCanvas['resolveModeRequest']>;
43
+ type GetCommandRegistryResult = ReturnType<PmxCanvas['getCommandRegistry']>;
44
+ type InvokeCommandResult = ReturnType<PmxCanvas['invokeCommand']>;
45
+ type GetPolicyResult = ReturnType<PmxCanvas['getPolicy']>;
46
+ type SetPolicyInput = Parameters<PmxCanvas['setPolicy']>[0];
47
+ type SetPolicyResult = ReturnType<PmxCanvas['setPolicy']>;
32
48
  type GetAxTimelineQuery = Parameters<PmxCanvas['getAxTimeline']>[0];
33
49
  type GetAxTimelineResult = ReturnType<PmxCanvas['getAxTimeline']>;
34
50
  type AddWorkItemInput = Parameters<PmxCanvas['addWorkItem']>[0];
@@ -139,6 +155,37 @@ export interface CanvasAccess {
139
155
  reportHostCapability(input: unknown, options?: {
140
156
  source?: PmxAxSource;
141
157
  }): Promise<ReportHostCapabilityResult>;
158
+ submitAxInteraction(input: SubmitAxInteractionInput, options?: {
159
+ source?: PmxAxSource;
160
+ }): Promise<SubmitAxInteractionResult>;
161
+ getPendingSteering(options?: {
162
+ consumer?: string;
163
+ limit?: number;
164
+ }): Promise<GetPendingSteeringResult>;
165
+ markSteeringDelivered(id: string): Promise<boolean>;
166
+ listElicitations(): Promise<ListElicitationsResult>;
167
+ requestElicitation(input: RequestElicitationInput, options?: {
168
+ source?: PmxAxSource;
169
+ }): Promise<RequestElicitationResult>;
170
+ respondElicitation(id: string, response: Record<string, unknown>, options?: {
171
+ source?: PmxAxSource;
172
+ }): Promise<RespondElicitationResult>;
173
+ listModeRequests(): Promise<ListModeRequestsResult>;
174
+ requestMode(input: RequestModeInput, options?: {
175
+ source?: PmxAxSource;
176
+ }): Promise<RequestModeResult>;
177
+ resolveModeRequest(id: string, decision: 'approved' | 'rejected', options?: {
178
+ resolution?: string;
179
+ source?: PmxAxSource;
180
+ }): Promise<ResolveModeRequestResult>;
181
+ getCommandRegistry(): Promise<GetCommandRegistryResult>;
182
+ invokeCommand(name: string, args?: Record<string, unknown> | null, options?: {
183
+ source?: PmxAxSource;
184
+ }): Promise<InvokeCommandResult>;
185
+ getPolicy(): Promise<GetPolicyResult>;
186
+ setPolicy(patch: SetPolicyInput, options?: {
187
+ source?: PmxAxSource;
188
+ }): Promise<SetPolicyResult>;
142
189
  clear(): Promise<void>;
143
190
  search(query: string): Promise<SearchResult>;
144
191
  undo(): Promise<UndoRedoResult>;
@@ -0,0 +1,210 @@
1
+ /**
2
+ * PMX-AX node interaction core (plan-004 Phase 1).
3
+ *
4
+ * One normalized envelope + capability model for node-originated AX interactions.
5
+ * Eligible nodes emit a validated `PmxAxInteraction`; this module checks the
6
+ * node's capabilities and payload, then maps the interaction onto the EXISTING
7
+ * AX operations (work items, evidence, approvals, review, focus, steering,
8
+ * events). It is host-agnostic and transport-agnostic — the same envelope backs
9
+ * native node events, json-render actions, the sandboxed HTML bridge, MCP apps,
10
+ * and host adapters (later phases).
11
+ *
12
+ * Decoupling: this module never imports the canvas-state singleton at runtime.
13
+ * The dispatcher takes the manager via dependency injection (structural
14
+ * `AxInteractionManager`), so it stays pure and unit-testable and introduces no
15
+ * import cycle (canvas-state → canvas-provenance must not pull this in).
16
+ */
17
+ import { z } from 'zod';
18
+ import type { CanvasNodeState } from './canvas-state.js';
19
+ import type { CanvasNodeType } from './canvas-provenance.js';
20
+ import type { PmxAxApprovalGate, PmxAxElicitation, PmxAxEvent, PmxAxEventKind, PmxAxEvidence, PmxAxEvidenceKind, PmxAxFocusState, PmxAxMode, PmxAxModeRequest, PmxAxReviewAnchorType, PmxAxReviewAnnotation, PmxAxReviewKind, PmxAxReviewRegion, PmxAxReviewSeverity, PmxAxSource, PmxAxSteeringMessage, PmxAxWorkItem, PmxAxWorkItemStatus } from './ax-state.js';
21
+ export declare const AX_INTERACTION_TYPES: readonly ["ax.event.record", "ax.steer", "ax.work.create", "ax.work.update", "ax.evidence.add", "ax.approval.request", "ax.approval.resolve", "ax.review.add", "ax.focus.set", "ax.command.invoke", "ax.elicitation.request", "ax.mode.request"];
22
+ export type AxInteractionType = (typeof AX_INTERACTION_TYPES)[number];
23
+ export type AxDeliveryMode = 'record-only' | 'notify-agent' | 'send-to-agent';
24
+ export interface NodeAxCapabilities {
25
+ enabled: boolean;
26
+ /** Interaction types this node may emit. Also the per-node override ceiling. */
27
+ allowed: AxInteractionType[];
28
+ /** Subset of `allowed` that should route through an approval gate (later phases). */
29
+ requiresApproval: AxInteractionType[];
30
+ delivery: AxDeliveryMode;
31
+ }
32
+ /**
33
+ * Server-side default (and per-node ceiling) capabilities per node type, from the
34
+ * plan's node capability matrix. `html`/`html-primitive`, `mcp-app`, and the
35
+ * internal `prompt`/`response` types default to disabled (opt-in / later phases);
36
+ * a node can anchor AX state but only eligible types may EMIT interactions.
37
+ */
38
+ export declare const DEFAULT_NODE_AX_CAPABILITIES: Record<CanvasNodeType, NodeAxCapabilities>;
39
+ /** Validate caller-supplied per-node `data.axCapabilities` into a partial override. */
40
+ export declare function normalizeNodeAxCapabilities(value: unknown): Partial<NodeAxCapabilities> | null;
41
+ /**
42
+ * Effective capabilities for a node: the type default merged with the node's own
43
+ * `data.axCapabilities`. A per-node override can toggle `enabled` and NARROW
44
+ * `allowed`, but never grant a type beyond the type's ceiling (security: a
45
+ * pasted/generated node cannot escalate itself).
46
+ */
47
+ export declare function resolveNodeAxCapabilities(node: CanvasNodeState): NodeAxCapabilities;
48
+ declare const InteractionEnvelopeSchema: z.ZodObject<{
49
+ type: z.ZodEnum<{
50
+ "ax.event.record": "ax.event.record";
51
+ "ax.steer": "ax.steer";
52
+ "ax.work.create": "ax.work.create";
53
+ "ax.work.update": "ax.work.update";
54
+ "ax.evidence.add": "ax.evidence.add";
55
+ "ax.approval.request": "ax.approval.request";
56
+ "ax.approval.resolve": "ax.approval.resolve";
57
+ "ax.review.add": "ax.review.add";
58
+ "ax.focus.set": "ax.focus.set";
59
+ "ax.command.invoke": "ax.command.invoke";
60
+ "ax.elicitation.request": "ax.elicitation.request";
61
+ "ax.mode.request": "ax.mode.request";
62
+ }>;
63
+ sourceNodeId: z.ZodString;
64
+ sourceSurface: z.ZodOptional<z.ZodEnum<{
65
+ "mcp-app": "mcp-app";
66
+ "json-render": "json-render";
67
+ "native-node": "native-node";
68
+ "html-node": "html-node";
69
+ adapter: "adapter";
70
+ }>>;
71
+ actor: z.ZodOptional<z.ZodObject<{
72
+ kind: z.ZodEnum<{
73
+ agent: "agent";
74
+ system: "system";
75
+ human: "human";
76
+ }>;
77
+ id: z.ZodOptional<z.ZodString>;
78
+ displayName: z.ZodOptional<z.ZodString>;
79
+ }, z.core.$strip>>;
80
+ payload: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
81
+ correlationId: z.ZodOptional<z.ZodString>;
82
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
83
+ }, z.core.$strip>;
84
+ export type PmxAxInteraction = z.infer<typeof InteractionEnvelopeSchema>;
85
+ /** Caller-facing interaction input (payload optional; validated on apply). */
86
+ export interface AxInteractionInput {
87
+ type: AxInteractionType;
88
+ sourceNodeId: string;
89
+ sourceSurface?: PmxAxInteraction['sourceSurface'];
90
+ actor?: PmxAxInteraction['actor'];
91
+ payload?: Record<string, unknown>;
92
+ correlationId?: string;
93
+ metadata?: Record<string, unknown>;
94
+ }
95
+ /**
96
+ * Structural subset of CanvasStateManager that interaction dispatch needs.
97
+ * Injected so this module stays free of a runtime canvas-state import.
98
+ */
99
+ export interface AxInteractionManager {
100
+ getNode(id: string): CanvasNodeState | undefined;
101
+ recordAxEvent(input: {
102
+ kind: PmxAxEventKind;
103
+ summary: string;
104
+ detail?: string | null;
105
+ nodeIds?: string[];
106
+ data?: Record<string, unknown> | null;
107
+ }, options?: {
108
+ source?: PmxAxSource;
109
+ }): PmxAxEvent;
110
+ recordSteeringMessage(message: string, options?: {
111
+ source?: PmxAxSource;
112
+ }): PmxAxSteeringMessage;
113
+ addWorkItem(input: {
114
+ title: string;
115
+ status?: PmxAxWorkItemStatus;
116
+ detail?: string | null;
117
+ nodeIds?: string[];
118
+ }, options?: {
119
+ source?: PmxAxSource;
120
+ }): PmxAxWorkItem;
121
+ updateWorkItem(id: string, patch: {
122
+ title?: string;
123
+ status?: PmxAxWorkItemStatus;
124
+ detail?: string | null;
125
+ nodeIds?: string[];
126
+ }, options?: {
127
+ source?: PmxAxSource;
128
+ }): PmxAxWorkItem | null;
129
+ addEvidence(input: {
130
+ kind: PmxAxEvidenceKind;
131
+ title: string;
132
+ body?: string | null;
133
+ ref?: string | null;
134
+ nodeIds?: string[];
135
+ data?: Record<string, unknown> | null;
136
+ }, options?: {
137
+ source?: PmxAxSource;
138
+ }): PmxAxEvidence;
139
+ requestApproval(input: {
140
+ title: string;
141
+ detail?: string | null;
142
+ action?: string | null;
143
+ nodeIds?: string[];
144
+ }, options?: {
145
+ source?: PmxAxSource;
146
+ }): PmxAxApprovalGate;
147
+ resolveApproval(id: string, decision: 'approved' | 'rejected', options?: {
148
+ resolution?: string;
149
+ source?: PmxAxSource;
150
+ }): PmxAxApprovalGate | null;
151
+ addReviewAnnotation(input: {
152
+ body: string;
153
+ kind?: PmxAxReviewKind;
154
+ severity?: PmxAxReviewSeverity;
155
+ anchorType?: PmxAxReviewAnchorType;
156
+ nodeId?: string | null;
157
+ file?: string | null;
158
+ region?: PmxAxReviewRegion | null;
159
+ author?: string | null;
160
+ }, options?: {
161
+ source?: PmxAxSource;
162
+ }): PmxAxReviewAnnotation | null;
163
+ setAxFocus(nodeIds: string[], options?: {
164
+ source?: PmxAxSource;
165
+ }): PmxAxFocusState;
166
+ requestElicitation(input: {
167
+ prompt: string;
168
+ fields?: string[];
169
+ nodeIds?: string[];
170
+ }, options?: {
171
+ source?: PmxAxSource;
172
+ }): PmxAxElicitation;
173
+ requestMode(input: {
174
+ mode: PmxAxMode;
175
+ reason?: string | null;
176
+ nodeIds?: string[];
177
+ }, options?: {
178
+ source?: PmxAxSource;
179
+ }): PmxAxModeRequest;
180
+ invokeCommand(name: string, args?: Record<string, unknown> | null, options?: {
181
+ source?: PmxAxSource;
182
+ }): PmxAxEvent | null;
183
+ }
184
+ export interface AxInteractionEvent {
185
+ event: string;
186
+ payload: Record<string, unknown>;
187
+ }
188
+ export type AxInteractionPublicResult = {
189
+ ok: true;
190
+ type: AxInteractionType;
191
+ sourceNodeId: string;
192
+ primitive: unknown;
193
+ } | {
194
+ ok: false;
195
+ status: number;
196
+ code: string;
197
+ error: string;
198
+ };
199
+ export interface AxInteractionResult {
200
+ result: AxInteractionPublicResult;
201
+ events: AxInteractionEvent[];
202
+ }
203
+ /**
204
+ * Validate + execute a node-originated AX interaction. Returns the public result
205
+ * plus the SSE events the caller should emit (accepted/rejected outcome + the
206
+ * underlying AX state event). Never throws on bad input — returns an `ok: false`
207
+ * result with an appropriate HTTP-ish status.
208
+ */
209
+ export declare function applyAxInteraction(manager: AxInteractionManager, rawBody: unknown, source: PmxAxSource): AxInteractionResult;
210
+ export {};
@@ -7,7 +7,7 @@ export interface PmxAxFocusState {
7
7
  updatedAt: string | null;
8
8
  source: PmxAxSource | null;
9
9
  }
10
- export type PmxAxEventKind = 'prompt' | 'assistant-message' | 'tool-start' | 'tool-result' | 'failure' | 'approval' | 'steering';
10
+ export type PmxAxEventKind = 'prompt' | 'assistant-message' | 'tool-start' | 'tool-result' | 'failure' | 'approval' | 'steering' | 'command';
11
11
  export type PmxAxEvidenceKind = 'logs' | 'tool-result' | 'screenshot' | 'file' | 'diff' | 'test-output';
12
12
  export type PmxAxWorkItemStatus = 'todo' | 'in-progress' | 'blocked' | 'done' | 'cancelled';
13
13
  export type PmxAxApprovalStatus = 'pending' | 'approved' | 'rejected';
@@ -122,6 +122,9 @@ export interface PmxAxState {
122
122
  workItems: PmxAxWorkItem[];
123
123
  approvalGates: PmxAxApprovalGate[];
124
124
  reviewAnnotations: PmxAxReviewAnnotation[];
125
+ elicitations: PmxAxElicitation[];
126
+ modeRequests: PmxAxModeRequest[];
127
+ policy: PmxAxPolicy;
125
128
  }
126
129
  export interface PmxAxPinnedContext {
127
130
  preamble: string;
@@ -144,12 +147,72 @@ export interface PmxAxContext {
144
147
  workItems: PmxAxWorkItem[];
145
148
  approvalGates: PmxAxApprovalGate[];
146
149
  reviewAnnotations: PmxAxReviewAnnotation[];
150
+ elicitations: PmxAxElicitation[];
151
+ modeRequests: PmxAxModeRequest[];
152
+ policy: PmxAxPolicy;
147
153
  timeline: PmxAxTimelineSummary;
148
154
  host: PmxAxHostCapability | null;
149
155
  }
156
+ export interface PmxAxCommandDescriptor {
157
+ name: string;
158
+ description: string;
159
+ }
160
+ export declare const AX_COMMAND_REGISTRY: Record<string, PmxAxCommandDescriptor>;
161
+ export declare function isAxCommand(name: unknown): name is string;
162
+ export declare function listAxCommands(): PmxAxCommandDescriptor[];
163
+ export interface PmxAxPolicy {
164
+ tools: {
165
+ allowed: string[];
166
+ excluded: string[];
167
+ approvalRequired: string[];
168
+ };
169
+ prompt: {
170
+ systemAppend: string | null;
171
+ mode: string | null;
172
+ };
173
+ }
174
+ export declare function createEmptyAxPolicy(): PmxAxPolicy;
175
+ export declare function normalizeAxPolicy(input: unknown): PmxAxPolicy;
150
176
  export declare function isAxEventKind(value: unknown): value is PmxAxEventKind;
151
177
  export declare function isAxEvidenceKind(value: unknown): value is PmxAxEvidenceKind;
152
178
  export declare function createEmptyAxFocusState(): PmxAxFocusState;
179
+ export type PmxAxElicitationStatus = 'pending' | 'answered' | 'cancelled';
180
+ export interface PmxAxElicitation {
181
+ id: string;
182
+ prompt: string;
183
+ fields: string[];
184
+ status: PmxAxElicitationStatus;
185
+ response: Record<string, unknown> | null;
186
+ nodeIds: string[];
187
+ createdAt: string;
188
+ resolvedAt: string | null;
189
+ source: PmxAxSource | null;
190
+ }
191
+ export declare function normalizeAxElicitation(input: unknown, validNodeIds?: Set<string>): PmxAxElicitation | null;
192
+ export declare function createAxElicitation(input: {
193
+ prompt: string;
194
+ fields?: string[];
195
+ nodeIds?: string[];
196
+ }, source: PmxAxSource | null, validNodeIds?: Set<string>): PmxAxElicitation;
197
+ export type PmxAxMode = 'plan' | 'execute' | 'autonomous';
198
+ export type PmxAxModeRequestStatus = 'pending' | 'approved' | 'rejected';
199
+ export interface PmxAxModeRequest {
200
+ id: string;
201
+ mode: PmxAxMode;
202
+ reason: string | null;
203
+ status: PmxAxModeRequestStatus;
204
+ nodeIds: string[];
205
+ createdAt: string;
206
+ resolvedAt: string | null;
207
+ resolution: string | null;
208
+ source: PmxAxSource | null;
209
+ }
210
+ export declare function normalizeAxModeRequest(input: unknown, validNodeIds?: Set<string>): PmxAxModeRequest | null;
211
+ export declare function createAxModeRequest(input: {
212
+ mode: PmxAxMode;
213
+ reason?: string | null;
214
+ nodeIds?: string[];
215
+ }, source: PmxAxSource | null, validNodeIds?: Set<string>): PmxAxModeRequest;
153
216
  export declare function createEmptyAxState(): PmxAxState;
154
217
  export declare function createEmptyAxHostCapability(): PmxAxHostCapability;
155
218
  export declare function normalizeAxFocusState(input: unknown, validNodeIds?: Set<string>): PmxAxFocusState;
@@ -207,6 +270,9 @@ export declare function buildAxContext(input: {
207
270
  workItems: PmxAxWorkItem[];
208
271
  approvalGates: PmxAxApprovalGate[];
209
272
  reviewAnnotations: PmxAxReviewAnnotation[];
273
+ elicitations: PmxAxElicitation[];
274
+ modeRequests: PmxAxModeRequest[];
275
+ policy: PmxAxPolicy;
210
276
  timeline: PmxAxTimelineSummary;
211
277
  host: PmxAxHostCapability | null;
212
278
  }): PmxAxContext;
@@ -49,6 +49,10 @@ export declare function loadAxEvidenceFromDB(db: Database, q?: AxTimelineQuery):
49
49
  export declare function loadAxSteeringFromDB(db: Database, q?: AxTimelineQuery & {
50
50
  onlyPending?: boolean;
51
51
  }): PmxAxSteeringMessage[];
52
+ export declare function loadPendingAxSteeringFromDB(db: Database, options?: {
53
+ consumer?: string;
54
+ limit?: number;
55
+ }): PmxAxSteeringMessage[];
52
56
  export declare function loadAxTimelineSummaryFromDB(db: Database): PmxAxTimelineSummary;
53
57
  export declare function upsertAxHostCapabilityToDB(db: Database, cap: PmxAxHostCapability): void;
54
58
  export declare function loadAxHostCapabilityFromDB(db: Database): PmxAxHostCapability | null;
@@ -6,6 +6,7 @@ export interface SerializedCanvasNode extends CanvasNodeState {
6
6
  content: string | null;
7
7
  path: string | null;
8
8
  url: string | null;
9
+ surfaceUrl: string | null;
9
10
  provenance: CanvasNodeProvenance | null;
10
11
  }
11
12
  export interface SerializedCanvasLayout extends Omit<CanvasLayout, 'nodes'> {
@@ -33,6 +34,7 @@ export interface CanvasAnnotationContextSummary {
33
34
  export declare function getCanvasNodeKind(node: CanvasNodeState, data: Record<string, unknown>): string;
34
35
  export declare function getCanvasNodeTitle(node: CanvasNodeState): string | null;
35
36
  export declare function getCanvasNodeContent(node: CanvasNodeState): string | null;
37
+ export declare function getCanvasNodeSurfaceUrl(node: CanvasNodeState, data: Record<string, unknown>): string | null;
36
38
  export declare function serializeCanvasNode(node: CanvasNodeState): SerializedCanvasNode;
37
39
  export declare function serializeCanvasNodeForAgent(node: CanvasNodeState): SerializedCanvasNode;
38
40
  export declare function serializeCanvasNodeCompact(node: CanvasNodeState): SerializedCanvasNode;
@@ -10,7 +10,7 @@
10
10
  * Legacy `.pmx-canvas/state.json` is auto-migrated on first boot.
11
11
  */
12
12
  import { type PersistedCanvasState, type CanvasTheme, type AxTimelineQuery } from './canvas-db.js';
13
- import { type PmxAxFocusState, type PmxAxSource, type PmxAxState, type PmxAxWorkItem, type PmxAxWorkItemStatus, type PmxAxApprovalGate, type PmxAxReviewAnnotation, type PmxAxReviewKind, type PmxAxReviewSeverity, type PmxAxReviewStatus, type PmxAxReviewAnchorType, type PmxAxReviewRegion, type PmxAxEvent, type PmxAxEventKind, type PmxAxEvidence, type PmxAxEvidenceKind, type PmxAxSteeringMessage, type PmxAxHostCapability, type PmxAxTimelineSummary } from './ax-state.js';
13
+ import { type PmxAxElicitation, type PmxAxModeRequest, type PmxAxMode, type PmxAxCommandDescriptor, type PmxAxPolicy, type PmxAxFocusState, type PmxAxSource, type PmxAxState, type PmxAxWorkItem, type PmxAxWorkItemStatus, type PmxAxApprovalGate, type PmxAxReviewAnnotation, type PmxAxReviewKind, type PmxAxReviewSeverity, type PmxAxReviewStatus, type PmxAxReviewAnchorType, type PmxAxReviewRegion, type PmxAxEvent, type PmxAxEventKind, type PmxAxEvidence, type PmxAxEvidenceKind, type PmxAxSteeringMessage, type PmxAxHostCapability, type PmxAxTimelineSummary } from './ax-state.js';
14
14
  export declare const PMX_CANVAS_DIR = ".pmx-canvas";
15
15
  export interface PersistedBlobRef {
16
16
  __pmxCanvasBlob: 'v1';
@@ -122,7 +122,7 @@ export interface CanvasNodeUpdate {
122
122
  }
123
123
  export type CanvasChangeType = 'pins' | 'nodes' | 'ax' | 'ax-timeline';
124
124
  export interface MutationRecordInfo {
125
- operationType: 'addNode' | 'updateNode' | 'removeNode' | 'addEdge' | 'removeEdge' | 'addAnnotation' | 'removeAnnotation' | 'clear' | 'restoreSnapshot' | 'setPins' | 'setAxFocus' | 'addWorkItem' | 'updateWorkItem' | 'requestApproval' | 'resolveApproval' | 'addReviewAnnotation' | 'updateReviewAnnotation' | 'arrange' | 'batch' | 'groupNodes' | 'ungroupNodes' | 'viewport';
125
+ operationType: 'addNode' | 'updateNode' | 'removeNode' | 'addEdge' | 'removeEdge' | 'addAnnotation' | 'removeAnnotation' | 'clear' | 'restoreSnapshot' | 'setPins' | 'setAxFocus' | 'addWorkItem' | 'updateWorkItem' | 'requestApproval' | 'resolveApproval' | 'addReviewAnnotation' | 'updateReviewAnnotation' | 'requestElicitation' | 'respondElicitation' | 'requestMode' | 'resolveModeRequest' | 'setPolicy' | 'arrange' | 'batch' | 'groupNodes' | 'ungroupNodes' | 'viewport';
126
126
  description: string;
127
127
  forward: () => void;
128
128
  inverse: () => void;
@@ -316,6 +316,42 @@ declare class CanvasStateManager {
316
316
  source?: PmxAxSource;
317
317
  }): PmxAxReviewAnnotation | null;
318
318
  getHostCapability(): PmxAxHostCapability | null;
319
+ getElicitations(): PmxAxElicitation[];
320
+ requestElicitation(input: {
321
+ prompt: string;
322
+ fields?: string[];
323
+ nodeIds?: string[];
324
+ }, options?: {
325
+ source?: PmxAxSource;
326
+ }): PmxAxElicitation;
327
+ respondElicitation(id: string, response: Record<string, unknown>, options?: {
328
+ source?: PmxAxSource;
329
+ }): PmxAxElicitation | null;
330
+ getModeRequests(): PmxAxModeRequest[];
331
+ requestMode(input: {
332
+ mode: PmxAxMode;
333
+ reason?: string | null;
334
+ nodeIds?: string[];
335
+ }, options?: {
336
+ source?: PmxAxSource;
337
+ }): PmxAxModeRequest;
338
+ resolveModeRequest(id: string, decision: 'approved' | 'rejected', options?: {
339
+ resolution?: string;
340
+ source?: PmxAxSource;
341
+ }): PmxAxModeRequest | null;
342
+ getCommandRegistry(): PmxAxCommandDescriptor[];
343
+ /** Invoke a registry-gated PMX command intent — records a timeline event (no execution). */
344
+ invokeCommand(name: string, args?: Record<string, unknown> | null, options?: {
345
+ source?: PmxAxSource;
346
+ }): PmxAxEvent | null;
347
+ getPolicy(): PmxAxPolicy;
348
+ /** Merge a declarative tool/prompt policy patch (canvas-bound, snapshotted). */
349
+ setPolicy(patch: {
350
+ tools?: Partial<PmxAxPolicy['tools']>;
351
+ prompt?: Partial<PmxAxPolicy['prompt']>;
352
+ }, _options?: {
353
+ source?: PmxAxSource;
354
+ }): PmxAxPolicy;
319
355
  setHostCapability(input: unknown, _options?: {
320
356
  source?: PmxAxSource;
321
357
  }): PmxAxHostCapability;
@@ -347,6 +383,15 @@ declare class CanvasStateManager {
347
383
  getAxSteering(q?: AxTimelineQuery & {
348
384
  onlyPending?: boolean;
349
385
  }): PmxAxSteeringMessage[];
386
+ /**
387
+ * Undelivered steering for a consumer (Phase 4 delivery). Excludes messages
388
+ * whose source equals the consumer to prevent delivery loops (e.g. Copilot
389
+ * should not be handed back steering it originated).
390
+ */
391
+ getPendingSteering(options?: {
392
+ consumer?: string;
393
+ limit?: number;
394
+ }): PmxAxSteeringMessage[];
350
395
  getAxTimelineSummary(): PmxAxTimelineSummary;
351
396
  getAxTimeline(q?: AxTimelineQuery): {
352
397
  events: PmxAxEvent[];
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Server-side builder for an `html` node's standalone surface document.
3
+ *
4
+ * This is the canonical wrapper that used to live in the client (HtmlNode's
5
+ * `buildSrcDoc`). It now lives on the server so a single document definition
6
+ * backs BOTH the in-canvas iframe and the "Open as site" tab — the iframe and
7
+ * the standalone tab load the exact same URL (/api/canvas/surface/:nodeId), so
8
+ * there is one render path and no content fork.
9
+ *
10
+ * Theming: instead of inlining a token `<style>` block, the document links the
11
+ * same-origin `/canvas/surface-theme.css` stylesheet and selects a palette via
12
+ * the `<html data-theme="...">` attribute. A sandboxed (opaque-origin) document
13
+ * can still load this same-origin stylesheet, and live theme switching works by
14
+ * toggling the attribute (the theme bridge below) — no CSS payload over
15
+ * postMessage required.
16
+ */
17
+ export type SurfaceTheme = 'dark' | 'light' | 'high-contrast';
18
+ /** Path the surface document links for its theme tokens (served from dist/canvas). */
19
+ export declare const SURFACE_THEME_STYLESHEET = "/canvas/surface-theme.css";
20
+ /** CSP sandbox tokens for an `html`/`html-primitive` surface — scripts only, opaque origin. */
21
+ export declare const HTML_SURFACE_SANDBOX = "allow-scripts";
22
+ export declare function normalizeSurfaceTheme(value: string | null | undefined): SurfaceTheme;
23
+ export interface HtmlSurfaceOptions {
24
+ theme: SurfaceTheme;
25
+ /** Client nonce that authorizes parent → iframe theme-update messages. */
26
+ themeToken?: string;
27
+ presentation?: boolean;
28
+ presentationExitToken?: string;
29
+ /** Inject window.PMX_AX.emit (only when the node's AX capabilities are enabled). */
30
+ axBridge?: boolean;
31
+ /** Nonce authorizing iframe → parent AX emits; embedded in the bridge. */
32
+ axToken?: string;
33
+ /** Node id stamped on emitted interactions. */
34
+ nodeId?: string;
35
+ }
36
+ /**
37
+ * Wrap author HTML into a complete, themed standalone document. Accepts either a
38
+ * full HTML document (injects into its `<head>`) or a fragment (wraps it).
39
+ */
40
+ export declare function buildHtmlSurfaceDocument(userHtml: string, options: HtmlSurfaceOptions): string;