pmx-canvas 0.2.7 → 0.3.1
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 +148 -1
- package/Readme.md +13 -11
- package/dist/canvas/index.js +2 -2
- package/dist/json-render/index.css +1 -1
- package/dist/types/cli/daemon.d.ts +74 -0
- package/dist/types/cli/watch.d.ts +2 -2
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
- package/dist/types/client/canvas/Minimap.d.ts +1 -1
- package/dist/types/client/nodes/FileNode.d.ts +1 -1
- package/dist/types/client/nodes/ImageNode.d.ts +1 -1
- package/dist/types/client/nodes/InlineFormatBar.d.ts +1 -1
- package/dist/types/client/nodes/MarkdownNode.d.ts +1 -1
- package/dist/types/client/nodes/PromptNode.d.ts +1 -1
- package/dist/types/client/nodes/ResponseNode.d.ts +1 -1
- package/dist/types/mcp/canvas-access.d.ts +0 -7
- package/dist/types/server/ax-state-manager.d.ts +3 -2
- package/dist/types/server/bundled-skills.d.ts +3 -3
- package/dist/types/server/canvas-schema.d.ts +1 -1
- package/dist/types/server/canvas-state.d.ts +2 -0
- package/dist/types/server/ext-app-lookup.d.ts +1 -3
- package/dist/types/server/html-primitives.d.ts +1 -1
- package/dist/types/server/index.d.ts +4 -4
- package/dist/types/server/operations/composites.d.ts +29 -23
- package/dist/types/server/operations/http.d.ts +2 -1
- package/dist/types/server/operations/index.d.ts +1 -1
- package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
- package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
- package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
- package/dist/types/shared/ax-intent.d.ts +1 -1
- package/docs/RELEASE.md +23 -8
- package/docs/ax-host-adapter-contract.md +7 -7
- package/docs/ax-state-contract.md +7 -6
- package/docs/bun-webview-integration.md +5 -5
- package/docs/cli.md +1 -1
- package/docs/http-api.md +35 -3
- package/docs/mcp.md +121 -100
- package/docs/node-types.md +45 -35
- package/docs/plans/plan-006-mcp-tool-consolidation.md +3 -1
- package/docs/plans/plan-008-registry-finish.md +2 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +51 -0
- package/docs/screenshot.png +0 -0
- package/docs/tech-debt-assessment-2026-06.md +1 -1
- package/docs/tech-debt-assessment-2026-07.md +135 -0
- package/package.json +5 -2
- package/skills/data-analysis/SKILL.md +3 -3
- package/skills/frontend-design/SKILL.md +3 -2
- package/skills/json-render-mcp/SKILL.md +4 -3
- package/skills/playwright-cli/SKILL.md +1 -1
- package/skills/pmx-canvas/SKILL.md +12 -7
- package/skills/pmx-canvas/evals/evals.json +2 -2
- package/skills/pmx-canvas/references/ax-html-control-surface.md +3 -1
- package/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +2 -2
- package/skills/pmx-canvas/references/full-reference.md +58 -56
- package/skills/pmx-canvas/references/html-primitives.md +3 -3
- package/skills/tufte-viz/SKILL.md +5 -4
- package/skills/web-artifacts-builder/SKILL.md +4 -4
- package/src/cli/agent.ts +1861 -1548
- package/src/cli/daemon.ts +460 -0
- package/src/cli/index.ts +63 -326
- package/src/cli/watch.ts +2 -10
- package/src/client/App.tsx +48 -46
- package/src/client/canvas/AttentionHistory.tsx +11 -1
- package/src/client/canvas/CanvasNode.tsx +41 -29
- package/src/client/canvas/CanvasViewport.tsx +101 -66
- package/src/client/canvas/CommandPalette.tsx +61 -27
- package/src/client/canvas/ContextMenu.tsx +13 -20
- package/src/client/canvas/ContextPinBar.tsx +1 -5
- package/src/client/canvas/ContextPinHud.tsx +1 -6
- package/src/client/canvas/DockedNode.tsx +4 -4
- package/src/client/canvas/EdgeLayer.tsx +37 -36
- package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
- package/src/client/canvas/FocusFieldLayer.tsx +20 -22
- package/src/client/canvas/IntentLayer.tsx +31 -14
- package/src/client/canvas/Minimap.tsx +11 -16
- package/src/client/canvas/SelectionBar.tsx +4 -11
- package/src/client/canvas/ShortcutOverlay.tsx +3 -1
- package/src/client/canvas/SnapshotPanel.tsx +77 -95
- package/src/client/canvas/auto-fit.ts +15 -14
- package/src/client/canvas/snap-guides.ts +12 -12
- package/src/client/canvas/use-node-resize.ts +1 -5
- package/src/client/canvas/use-pan-zoom.ts +25 -26
- package/src/client/ext-app/bridge.ts +3 -12
- package/src/client/icons.tsx +63 -20
- package/src/client/nodes/ContextNode.tsx +14 -25
- package/src/client/nodes/ExtAppFrame.tsx +60 -39
- package/src/client/nodes/FileNode.tsx +74 -62
- package/src/client/nodes/GroupNode.tsx +4 -6
- package/src/client/nodes/HtmlNode.tsx +76 -46
- package/src/client/nodes/ImageNode.tsx +18 -27
- package/src/client/nodes/InlineFormatBar.tsx +4 -21
- package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
- package/src/client/nodes/LedgerNode.tsx +10 -4
- package/src/client/nodes/MarkdownNode.tsx +3 -10
- package/src/client/nodes/McpAppNode.tsx +26 -22
- package/src/client/nodes/MdFormatBar.tsx +10 -7
- package/src/client/nodes/PromptNode.tsx +23 -51
- package/src/client/nodes/ResponseNode.tsx +3 -13
- package/src/client/nodes/StatusNode.tsx +5 -9
- package/src/client/nodes/StatusSummary.tsx +2 -8
- package/src/client/nodes/WebpageNode.tsx +20 -14
- package/src/client/nodes/iframe-document-url.ts +25 -16
- package/src/client/nodes/image-warnings.ts +1 -7
- package/src/client/nodes/md-format.ts +20 -5
- package/src/client/state/attention-bridge.ts +4 -9
- package/src/client/state/attention-store.ts +1 -7
- package/src/client/state/canvas-store.ts +52 -36
- package/src/client/state/intent-bridge.ts +176 -112
- package/src/client/state/intent-store.ts +4 -1
- package/src/client/state/sse-bridge.ts +53 -70
- package/src/json-render/catalog.ts +12 -16
- package/src/json-render/charts/components.tsx +16 -20
- package/src/json-render/charts/extra-components.tsx +8 -16
- package/src/json-render/charts/extra-definitions.ts +1 -2
- package/src/json-render/charts/tufte-components.tsx +37 -20
- package/src/json-render/charts/tufte-definitions.ts +8 -2
- package/src/json-render/renderer/index.tsx +42 -22
- package/src/json-render/schema.ts +6 -3
- package/src/json-render/server.ts +33 -39
- package/src/mcp/canvas-access.ts +40 -121
- package/src/mcp/server.ts +206 -271
- package/src/server/agent-context.ts +63 -36
- package/src/server/ax-context.ts +7 -5
- package/src/server/ax-interaction.ts +176 -43
- package/src/server/ax-state-manager.ts +185 -41
- package/src/server/ax-state.ts +142 -47
- package/src/server/bundled-skills.ts +3 -3
- package/src/server/canvas-db.ts +213 -95
- package/src/server/canvas-operations.ts +177 -120
- package/src/server/canvas-provenance.ts +1 -4
- package/src/server/canvas-schema.ts +500 -102
- package/src/server/canvas-serialization.ts +27 -35
- package/src/server/canvas-state.ts +155 -58
- package/src/server/chart-template.ts +4 -5
- package/src/server/code-graph.ts +20 -7
- package/src/server/diagram-presets.ts +28 -29
- package/src/server/ext-app-lookup.ts +4 -20
- package/src/server/html-node-summary.ts +19 -10
- package/src/server/html-primitives.ts +326 -97
- package/src/server/html-surface.ts +6 -9
- package/src/server/image-source.ts +6 -4
- package/src/server/index.ts +321 -218
- package/src/server/intent-registry.ts +3 -6
- package/src/server/mcp-app-candidate.ts +5 -10
- package/src/server/mcp-app-host.ts +14 -38
- package/src/server/mcp-app-runtime.ts +12 -20
- package/src/server/mutation-history.ts +15 -5
- package/src/server/operations/composites.ts +38 -40
- package/src/server/operations/http.ts +5 -5
- package/src/server/operations/index.ts +7 -1
- package/src/server/operations/invoker.ts +4 -3
- package/src/server/operations/mcp.ts +29 -36
- package/src/server/operations/ops/annotation.ts +122 -10
- package/src/server/operations/ops/app.ts +102 -77
- package/src/server/operations/ops/ax-await.ts +17 -10
- package/src/server/operations/ops/ax-read.ts +347 -0
- package/src/server/operations/ops/ax-shared.ts +2 -7
- package/src/server/operations/ops/ax-state.ts +32 -14
- package/src/server/operations/ops/ax-timeline.ts +32 -19
- package/src/server/operations/ops/ax-work.ts +54 -37
- package/src/server/operations/ops/batch.ts +39 -14
- package/src/server/operations/ops/canvas-wire.ts +91 -0
- package/src/server/operations/ops/edges.ts +37 -25
- package/src/server/operations/ops/ext-app.ts +346 -0
- package/src/server/operations/ops/groups.ts +49 -20
- package/src/server/operations/ops/intent.ts +18 -12
- package/src/server/operations/ops/json-render.ts +239 -98
- package/src/server/operations/ops/nodes.ts +300 -111
- package/src/server/operations/ops/query.ts +62 -33
- package/src/server/operations/ops/snapshots.ts +35 -26
- package/src/server/operations/ops/validate.ts +2 -1
- package/src/server/operations/ops/viewport.ts +60 -16
- package/src/server/operations/ops/webview.ts +44 -18
- package/src/server/operations/registry.ts +2 -3
- package/src/server/operations/types.ts +7 -5
- package/src/server/operations/webview-runner.ts +1 -3
- package/src/server/placement.ts +8 -18
- package/src/server/server.ts +121 -1012
- package/src/server/spatial-analysis.ts +39 -25
- package/src/server/trace-manager.ts +3 -8
- package/src/server/web-artifacts.ts +23 -27
- package/src/server/webpage-node.ts +5 -13
- package/src/shared/auto-arrange.ts +12 -5
- package/src/shared/ax-intent.ts +1 -1
- package/src/shared/content-height-reporter.ts +8 -6
- package/src/shared/ext-app-tool-result.ts +2 -6
- package/src/shared/placement.ts +1 -4
- package/src/shared/semantic-attention.ts +39 -37
- package/src/shared/surface.ts +8 -4
|
@@ -56,11 +56,8 @@ function summarizeWebpageData(data: Record<string, unknown>, maxLength: number):
|
|
|
56
56
|
const status = typeof data.status === 'string' ? data.status : '';
|
|
57
57
|
const statusCode = typeof data.statusCode === 'number' ? data.statusCode : null;
|
|
58
58
|
const error = typeof data.error === 'string' ? data.error : '';
|
|
59
|
-
const content =
|
|
60
|
-
? data.content
|
|
61
|
-
: typeof data.excerpt === 'string'
|
|
62
|
-
? data.excerpt
|
|
63
|
-
: '';
|
|
59
|
+
const content =
|
|
60
|
+
typeof data.content === 'string' ? data.content : typeof data.excerpt === 'string' ? data.excerpt : '';
|
|
64
61
|
|
|
65
62
|
if (url) parts.push(`URL: ${url}`);
|
|
66
63
|
if (pageTitle) parts.push(`Title: ${pageTitle}`);
|
|
@@ -132,9 +129,7 @@ function summarizeWebArtifactData(data: Record<string, unknown>, maxLength: numb
|
|
|
132
129
|
const sourceFiles = Array.isArray(data.sourceFiles)
|
|
133
130
|
? data.sourceFiles.filter((file): file is string => typeof file === 'string')
|
|
134
131
|
: [];
|
|
135
|
-
const deps = Array.isArray(data.deps)
|
|
136
|
-
? data.deps.filter((dep): dep is string => typeof dep === 'string')
|
|
137
|
-
: [];
|
|
132
|
+
const deps = Array.isArray(data.deps) ? data.deps.filter((dep): dep is string => typeof dep === 'string') : [];
|
|
138
133
|
|
|
139
134
|
if (content) parts.push(content);
|
|
140
135
|
if (!content && title) parts.push(`Web artifact: ${title}`);
|
|
@@ -170,7 +165,16 @@ function metadataForNode(node: CanvasNodeState): Record<string, unknown> | undef
|
|
|
170
165
|
switch (node.type) {
|
|
171
166
|
case 'webpage': {
|
|
172
167
|
const metadata: Record<string, unknown> = {};
|
|
173
|
-
for (const key of [
|
|
168
|
+
for (const key of [
|
|
169
|
+
'url',
|
|
170
|
+
'pageTitle',
|
|
171
|
+
'description',
|
|
172
|
+
'imageUrl',
|
|
173
|
+
'fetchedAt',
|
|
174
|
+
'status',
|
|
175
|
+
'statusCode',
|
|
176
|
+
'contentType',
|
|
177
|
+
]) {
|
|
174
178
|
const value = node.data[key];
|
|
175
179
|
if (value !== undefined && value !== null && value !== '') metadata[key] = value;
|
|
176
180
|
}
|
|
@@ -194,7 +198,19 @@ function metadataForNode(node: CanvasNodeState): Record<string, unknown> | undef
|
|
|
194
198
|
}
|
|
195
199
|
case 'html': {
|
|
196
200
|
const metadata: Record<string, unknown> = {};
|
|
197
|
-
for (const key of [
|
|
201
|
+
for (const key of [
|
|
202
|
+
'summary',
|
|
203
|
+
'description',
|
|
204
|
+
'agentSummary',
|
|
205
|
+
'contentSummary',
|
|
206
|
+
'htmlPrimitive',
|
|
207
|
+
'presentation',
|
|
208
|
+
'slideCount',
|
|
209
|
+
'slideTitles',
|
|
210
|
+
'speakerNotes',
|
|
211
|
+
'embeddedNodeIds',
|
|
212
|
+
'embeddedUrls',
|
|
213
|
+
]) {
|
|
198
214
|
const value = node.data[key];
|
|
199
215
|
if (Array.isArray(value)) {
|
|
200
216
|
if (value.length > 0) metadata[key] = value;
|
|
@@ -206,7 +222,22 @@ function metadataForNode(node: CanvasNodeState): Record<string, unknown> | undef
|
|
|
206
222
|
}
|
|
207
223
|
case 'mcp-app': {
|
|
208
224
|
const metadata: Record<string, unknown> = {};
|
|
209
|
-
for (const key of [
|
|
225
|
+
for (const key of [
|
|
226
|
+
'url',
|
|
227
|
+
'path',
|
|
228
|
+
'mode',
|
|
229
|
+
'hostMode',
|
|
230
|
+
'viewerType',
|
|
231
|
+
'serverName',
|
|
232
|
+
'toolName',
|
|
233
|
+
'resourceUri',
|
|
234
|
+
'sessionStatus',
|
|
235
|
+
'projectPath',
|
|
236
|
+
'artifactBytes',
|
|
237
|
+
'sourceFiles',
|
|
238
|
+
'sourceFileCount',
|
|
239
|
+
'deps',
|
|
240
|
+
]) {
|
|
210
241
|
const value = node.data[key];
|
|
211
242
|
if (Array.isArray(value)) {
|
|
212
243
|
if (value.length > 0) metadata[key] = value;
|
|
@@ -221,10 +252,7 @@ function metadataForNode(node: CanvasNodeState): Record<string, unknown> | undef
|
|
|
221
252
|
}
|
|
222
253
|
}
|
|
223
254
|
|
|
224
|
-
export function summarizeNodeForAgentContext(
|
|
225
|
-
node: CanvasNodeState,
|
|
226
|
-
options: AgentContextOptions = {},
|
|
227
|
-
): string {
|
|
255
|
+
export function summarizeNodeForAgentContext(node: CanvasNodeState, options: AgentContextOptions = {}): string {
|
|
228
256
|
const defaultTextLength = options.defaultTextLength ?? DEFAULT_CONTEXT_TEXT_LENGTH;
|
|
229
257
|
const webpageTextLength = options.webpageTextLength ?? DEFAULT_WEBPAGE_CONTEXT_TEXT_LENGTH;
|
|
230
258
|
|
|
@@ -241,9 +269,7 @@ export function summarizeNodeForAgentContext(
|
|
|
241
269
|
if (chartCfg) {
|
|
242
270
|
const chartTitle = (chartCfg.title as string) || 'Untitled chart';
|
|
243
271
|
const chartType = (chartCfg.type as string) || 'unknown';
|
|
244
|
-
const labels = Array.isArray(chartCfg.labels)
|
|
245
|
-
? (chartCfg.labels as string[]).join(', ')
|
|
246
|
-
: '';
|
|
272
|
+
const labels = Array.isArray(chartCfg.labels) ? (chartCfg.labels as string[]).join(', ') : '';
|
|
247
273
|
return truncateContextText(`Chart: ${chartTitle} (${chartType}). Labels: ${labels}`, defaultTextLength);
|
|
248
274
|
}
|
|
249
275
|
return summarizeMcpAppData(node.data, defaultTextLength);
|
|
@@ -263,14 +289,17 @@ export function summarizeNodeForAgentContext(
|
|
|
263
289
|
if (typeof node.data.htmlPrimitive === 'string') {
|
|
264
290
|
return summarizeHtmlPrimitiveData(node.data, defaultTextLength);
|
|
265
291
|
}
|
|
266
|
-
return stringifyContextValue(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
292
|
+
return stringifyContextValue(
|
|
293
|
+
{
|
|
294
|
+
title: node.data.title,
|
|
295
|
+
description: node.data.description,
|
|
296
|
+
summary: node.data.summary,
|
|
297
|
+
contentSummary: node.data.contentSummary,
|
|
298
|
+
embeddedNodeIds: node.data.embeddedNodeIds,
|
|
299
|
+
embeddedUrls: node.data.embeddedUrls,
|
|
300
|
+
},
|
|
301
|
+
defaultTextLength,
|
|
302
|
+
);
|
|
274
303
|
}
|
|
275
304
|
case 'prompt':
|
|
276
305
|
case 'response': {
|
|
@@ -279,11 +308,12 @@ export function summarizeNodeForAgentContext(
|
|
|
279
308
|
}
|
|
280
309
|
case 'file': {
|
|
281
310
|
const path = typeof node.data.path === 'string' ? node.data.path : '';
|
|
282
|
-
const fileContent =
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
311
|
+
const fileContent =
|
|
312
|
+
typeof node.data.fileContent === 'string'
|
|
313
|
+
? node.data.fileContent
|
|
314
|
+
: typeof node.data.content === 'string'
|
|
315
|
+
? node.data.content
|
|
316
|
+
: '';
|
|
287
317
|
const prefix = path ? `Path: ${path}\n\n` : '';
|
|
288
318
|
const remaining = Math.max(0, defaultTextLength - prefix.length);
|
|
289
319
|
return `${prefix}${truncateContextText(fileContent, remaining)}`.trim();
|
|
@@ -309,13 +339,10 @@ export function serializeNodeForAgentContext(
|
|
|
309
339
|
};
|
|
310
340
|
}
|
|
311
341
|
|
|
312
|
-
export function buildAgentContextPreamble(
|
|
313
|
-
nodes: CanvasNodeState[],
|
|
314
|
-
options: AgentContextOptions = {},
|
|
315
|
-
): string {
|
|
342
|
+
export function buildAgentContextPreamble(nodes: CanvasNodeState[], options: AgentContextOptions = {}): string {
|
|
316
343
|
const sections = nodes
|
|
317
344
|
.map((node) => {
|
|
318
|
-
const title =
|
|
345
|
+
const title = typeof node.data.title === 'string' && node.data.title ? node.data.title : node.id;
|
|
319
346
|
const content = summarizeNodeForAgentContext(node, options);
|
|
320
347
|
if (!content) return '';
|
|
321
348
|
const kind = getCanvasNodeKind(node);
|
package/src/server/ax-context.ts
CHANGED
|
@@ -53,11 +53,13 @@ export function buildCanvasAxSurfaceSnapshot(): PmxAxSurfaceSnapshot {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
function serializeNodes(nodes: CanvasNodeState[]) {
|
|
56
|
-
return nodes.map((node) =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
return nodes.map((node) =>
|
|
57
|
+
serializeNodeForAgentContext(node, {
|
|
58
|
+
defaultTextLength: 700,
|
|
59
|
+
webpageTextLength: 1600,
|
|
60
|
+
includePosition: true,
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
export function buildCanvasAxPinnedContext(): PmxAxPinnedContext {
|
|
@@ -86,11 +86,29 @@ function caps(
|
|
|
86
86
|
* a node can anchor AX state but only eligible types may EMIT interactions.
|
|
87
87
|
*/
|
|
88
88
|
export const DEFAULT_NODE_AX_CAPABILITIES: Record<CanvasNodeType, NodeAxCapabilities> = {
|
|
89
|
-
markdown: caps(
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
markdown: caps(
|
|
90
|
+
true,
|
|
91
|
+
['ax.steer', 'ax.work.create', 'ax.evidence.add', 'ax.command.invoke', 'ax.event.record'],
|
|
92
|
+
'notify-agent',
|
|
93
|
+
),
|
|
94
|
+
context: caps(
|
|
95
|
+
true,
|
|
96
|
+
['ax.focus.set', 'ax.steer', 'ax.evidence.add', 'ax.command.invoke', 'ax.event.record'],
|
|
97
|
+
'notify-agent',
|
|
98
|
+
),
|
|
99
|
+
status: caps(
|
|
100
|
+
true,
|
|
101
|
+
['ax.work.create', 'ax.work.update', 'ax.approval.request', 'ax.mode.request', 'ax.event.record'],
|
|
102
|
+
'notify-agent',
|
|
103
|
+
),
|
|
92
104
|
file: caps(true, ['ax.evidence.add', 'ax.review.add', 'ax.focus.set', 'ax.event.record']),
|
|
93
|
-
'json-render': caps(true, [
|
|
105
|
+
'json-render': caps(true, [
|
|
106
|
+
'ax.work.create',
|
|
107
|
+
'ax.work.update',
|
|
108
|
+
'ax.evidence.add',
|
|
109
|
+
'ax.elicitation.request',
|
|
110
|
+
'ax.event.record',
|
|
111
|
+
]),
|
|
94
112
|
graph: caps(true, ['ax.evidence.add', 'ax.focus.set', 'ax.event.record']),
|
|
95
113
|
ledger: caps(true, ['ax.evidence.add', 'ax.event.record']),
|
|
96
114
|
trace: caps(true, ['ax.evidence.add', 'ax.event.record']),
|
|
@@ -123,8 +141,12 @@ export const DEFAULT_NODE_AX_CAPABILITIES: Record<CanvasNodeType, NodeAxCapabili
|
|
|
123
141
|
// itself, and request human input. Excludes higher-trust types (steer, approval,
|
|
124
142
|
// review, command, mode) which stay native-control / adapter only.
|
|
125
143
|
'mcp-app': caps(false, [
|
|
126
|
-
'ax.event.record',
|
|
127
|
-
'ax.
|
|
144
|
+
'ax.event.record',
|
|
145
|
+
'ax.evidence.add',
|
|
146
|
+
'ax.work.create',
|
|
147
|
+
'ax.work.update',
|
|
148
|
+
'ax.focus.set',
|
|
149
|
+
'ax.elicitation.request',
|
|
128
150
|
]),
|
|
129
151
|
// Internal thread nodes — anchor only, no human-facing emission by default.
|
|
130
152
|
prompt: caps(false, ['ax.event.record']),
|
|
@@ -141,11 +163,13 @@ export function normalizeNodeAxCapabilities(value: unknown): Partial<NodeAxCapab
|
|
|
141
163
|
if (typeof v.enabled === 'boolean') out.enabled = v.enabled;
|
|
142
164
|
if (Array.isArray(v.allowed)) {
|
|
143
165
|
out.allowed = v.allowed.filter((a): a is AxInteractionType =>
|
|
144
|
-
AX_INTERACTION_TYPES.includes(a as AxInteractionType)
|
|
166
|
+
AX_INTERACTION_TYPES.includes(a as AxInteractionType),
|
|
167
|
+
);
|
|
145
168
|
}
|
|
146
169
|
if (Array.isArray(v.requiresApproval)) {
|
|
147
170
|
out.requiresApproval = v.requiresApproval.filter((a): a is AxInteractionType =>
|
|
148
|
-
AX_INTERACTION_TYPES.includes(a as AxInteractionType)
|
|
171
|
+
AX_INTERACTION_TYPES.includes(a as AxInteractionType),
|
|
172
|
+
);
|
|
149
173
|
}
|
|
150
174
|
if (v.delivery === 'record-only' || v.delivery === 'notify-agent' || v.delivery === 'send-to-agent') {
|
|
151
175
|
out.delivery = v.delivery;
|
|
@@ -172,7 +196,15 @@ export function resolveNodeAxCapabilities(node: CanvasNodeState): NodeAxCapabili
|
|
|
172
196
|
|
|
173
197
|
// ── Envelope + payload validation ──────────────────────────────
|
|
174
198
|
|
|
175
|
-
const EVENT_KINDS = [
|
|
199
|
+
const EVENT_KINDS = [
|
|
200
|
+
'prompt',
|
|
201
|
+
'assistant-message',
|
|
202
|
+
'tool-start',
|
|
203
|
+
'tool-result',
|
|
204
|
+
'failure',
|
|
205
|
+
'approval',
|
|
206
|
+
'steering',
|
|
207
|
+
] as const;
|
|
176
208
|
const EVIDENCE_KINDS = ['logs', 'tool-result', 'screenshot', 'file', 'diff', 'test-output'] as const;
|
|
177
209
|
const WORK_STATUSES = ['todo', 'in-progress', 'blocked', 'done', 'cancelled'] as const;
|
|
178
210
|
const REVIEW_KINDS = ['comment', 'finding'] as const;
|
|
@@ -284,7 +316,13 @@ const PAYLOAD_SCHEMAS: Record<string, z.ZodType> = {
|
|
|
284
316
|
export interface AxInteractionManager {
|
|
285
317
|
getNode(id: string): CanvasNodeState | undefined;
|
|
286
318
|
recordAxEvent(
|
|
287
|
-
input: {
|
|
319
|
+
input: {
|
|
320
|
+
kind: PmxAxEventKind;
|
|
321
|
+
summary: string;
|
|
322
|
+
detail?: string | null;
|
|
323
|
+
nodeIds?: string[];
|
|
324
|
+
data?: Record<string, unknown> | null;
|
|
325
|
+
},
|
|
288
326
|
options?: { source?: PmxAxSource },
|
|
289
327
|
): PmxAxEvent;
|
|
290
328
|
recordSteeringMessage(message: string, options?: { source?: PmxAxSource }): PmxAxSteeringMessage;
|
|
@@ -298,7 +336,14 @@ export interface AxInteractionManager {
|
|
|
298
336
|
options?: { source?: PmxAxSource },
|
|
299
337
|
): PmxAxWorkItem | null;
|
|
300
338
|
addEvidence(
|
|
301
|
-
input: {
|
|
339
|
+
input: {
|
|
340
|
+
kind: PmxAxEvidenceKind;
|
|
341
|
+
title: string;
|
|
342
|
+
body?: string | null;
|
|
343
|
+
ref?: string | null;
|
|
344
|
+
nodeIds?: string[];
|
|
345
|
+
data?: Record<string, unknown> | null;
|
|
346
|
+
},
|
|
302
347
|
options?: { source?: PmxAxSource },
|
|
303
348
|
): PmxAxEvidence;
|
|
304
349
|
requestApproval(
|
|
@@ -332,7 +377,11 @@ export interface AxInteractionManager {
|
|
|
332
377
|
input: { mode: PmxAxMode; reason?: string | null; nodeIds?: string[] },
|
|
333
378
|
options?: { source?: PmxAxSource },
|
|
334
379
|
): PmxAxModeRequest;
|
|
335
|
-
invokeCommand(
|
|
380
|
+
invokeCommand(
|
|
381
|
+
name: string,
|
|
382
|
+
args?: Record<string, unknown> | null,
|
|
383
|
+
options?: { source?: PmxAxSource },
|
|
384
|
+
): PmxAxEvent | null;
|
|
336
385
|
}
|
|
337
386
|
|
|
338
387
|
export interface AxInteractionEvent {
|
|
@@ -353,13 +402,7 @@ function outcomeEvent(extra: Record<string, unknown>): AxInteractionEvent {
|
|
|
353
402
|
return { event: 'ax-interaction', payload: extra };
|
|
354
403
|
}
|
|
355
404
|
|
|
356
|
-
function reject(
|
|
357
|
-
type: string,
|
|
358
|
-
sourceNodeId: string,
|
|
359
|
-
status: number,
|
|
360
|
-
code: string,
|
|
361
|
-
error: string,
|
|
362
|
-
): AxInteractionResult {
|
|
405
|
+
function reject(type: string, sourceNodeId: string, status: number, code: string, error: string): AxInteractionResult {
|
|
363
406
|
return {
|
|
364
407
|
result: { ok: false, status, code, error },
|
|
365
408
|
events: [outcomeEvent({ ok: false, type, sourceNodeId, code, error })],
|
|
@@ -375,10 +418,7 @@ function accept(
|
|
|
375
418
|
): AxInteractionResult {
|
|
376
419
|
return {
|
|
377
420
|
result: { ok: true, type, sourceNodeId, primitive },
|
|
378
|
-
events: [
|
|
379
|
-
outcomeEvent({ ok: true, type, sourceNodeId }),
|
|
380
|
-
{ event: stateEvent, payload: statePayload },
|
|
381
|
-
],
|
|
421
|
+
events: [outcomeEvent({ ok: true, type, sourceNodeId }), { event: stateEvent, payload: statePayload }],
|
|
382
422
|
};
|
|
383
423
|
}
|
|
384
424
|
|
|
@@ -396,10 +436,14 @@ export function applyAxInteraction(
|
|
|
396
436
|
const parsed = InteractionEnvelopeSchema.safeParse(rawBody);
|
|
397
437
|
if (!parsed.success) {
|
|
398
438
|
const error = parsed.error.issues.map((i) => `${i.path.join('.') || 'envelope'}: ${i.message}`).join('; ');
|
|
399
|
-
const type =
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
439
|
+
const type =
|
|
440
|
+
typeof (rawBody as { type?: unknown })?.type === 'string'
|
|
441
|
+
? String((rawBody as { type?: unknown }).type)
|
|
442
|
+
: 'unknown';
|
|
443
|
+
const sourceNodeId =
|
|
444
|
+
typeof (rawBody as { sourceNodeId?: unknown })?.sourceNodeId === 'string'
|
|
445
|
+
? String((rawBody as { sourceNodeId?: unknown }).sourceNodeId)
|
|
446
|
+
: '';
|
|
403
447
|
return reject(type, sourceNodeId, 400, 'invalid-envelope', error);
|
|
404
448
|
}
|
|
405
449
|
const interaction = parsed.data;
|
|
@@ -410,7 +454,13 @@ export function applyAxInteraction(
|
|
|
410
454
|
|
|
411
455
|
const capabilities = resolveNodeAxCapabilities(node);
|
|
412
456
|
if (!capabilities.enabled) {
|
|
413
|
-
return reject(
|
|
457
|
+
return reject(
|
|
458
|
+
type,
|
|
459
|
+
sourceNodeId,
|
|
460
|
+
403,
|
|
461
|
+
'ax-disabled',
|
|
462
|
+
`AX interactions are not enabled for node "${sourceNodeId}".`,
|
|
463
|
+
);
|
|
414
464
|
}
|
|
415
465
|
if (!capabilities.allowed.includes(type)) {
|
|
416
466
|
return reject(type, sourceNodeId, 403, 'not-allowed', `Node type "${node.type}" cannot emit "${type}".`);
|
|
@@ -418,7 +468,13 @@ export function applyAxInteraction(
|
|
|
418
468
|
// Fail closed: approval-gated interaction types are rejected until approval
|
|
419
469
|
// routing lands, rather than dispatched without the gate they require.
|
|
420
470
|
if (capabilities.requiresApproval.includes(type)) {
|
|
421
|
-
return reject(
|
|
471
|
+
return reject(
|
|
472
|
+
type,
|
|
473
|
+
sourceNodeId,
|
|
474
|
+
403,
|
|
475
|
+
'requires-approval',
|
|
476
|
+
`"${type}" requires approval routing, which is not yet available.`,
|
|
477
|
+
);
|
|
422
478
|
}
|
|
423
479
|
|
|
424
480
|
const schema = PAYLOAD_SCHEMAS[type];
|
|
@@ -446,9 +502,21 @@ export function applyAxInteraction(
|
|
|
446
502
|
|
|
447
503
|
switch (type) {
|
|
448
504
|
case 'ax.event.record': {
|
|
449
|
-
const p = payloadParsed.data as {
|
|
505
|
+
const p = payloadParsed.data as {
|
|
506
|
+
kind: PmxAxEventKind;
|
|
507
|
+
summary: string;
|
|
508
|
+
detail?: string | null;
|
|
509
|
+
nodeIds?: string[];
|
|
510
|
+
data?: Record<string, unknown> | null;
|
|
511
|
+
};
|
|
450
512
|
const event = manager.recordAxEvent(
|
|
451
|
-
{
|
|
513
|
+
{
|
|
514
|
+
kind: p.kind,
|
|
515
|
+
summary: p.summary,
|
|
516
|
+
detail: p.detail ?? null,
|
|
517
|
+
nodeIds: scopedNodeIds(p.nodeIds),
|
|
518
|
+
data: p.data ?? null,
|
|
519
|
+
},
|
|
452
520
|
opts,
|
|
453
521
|
);
|
|
454
522
|
return accept(type, sourceNodeId, event, 'ax-event-created', { event });
|
|
@@ -459,15 +527,31 @@ export function applyAxInteraction(
|
|
|
459
527
|
return accept(type, sourceNodeId, steering, 'ax-event-created', { steering });
|
|
460
528
|
}
|
|
461
529
|
case 'ax.work.create': {
|
|
462
|
-
const p = payloadParsed.data as {
|
|
530
|
+
const p = payloadParsed.data as {
|
|
531
|
+
title: string;
|
|
532
|
+
status?: PmxAxWorkItemStatus;
|
|
533
|
+
detail?: string | null;
|
|
534
|
+
nodeIds?: string[];
|
|
535
|
+
};
|
|
463
536
|
const workItem = manager.addWorkItem(
|
|
464
|
-
{
|
|
537
|
+
{
|
|
538
|
+
title: p.title,
|
|
539
|
+
...(p.status ? { status: p.status } : {}),
|
|
540
|
+
...(p.detail !== undefined ? { detail: p.detail } : {}),
|
|
541
|
+
nodeIds: scopedNodeIds(p.nodeIds),
|
|
542
|
+
},
|
|
465
543
|
opts,
|
|
466
544
|
);
|
|
467
545
|
return accept(type, sourceNodeId, workItem, 'ax-state-changed', { workItem });
|
|
468
546
|
}
|
|
469
547
|
case 'ax.work.update': {
|
|
470
|
-
const p = payloadParsed.data as {
|
|
548
|
+
const p = payloadParsed.data as {
|
|
549
|
+
id: string;
|
|
550
|
+
title?: string;
|
|
551
|
+
status?: PmxAxWorkItemStatus;
|
|
552
|
+
detail?: string | null;
|
|
553
|
+
nodeIds?: string[];
|
|
554
|
+
};
|
|
471
555
|
const { id, ...patch } = p;
|
|
472
556
|
if (scoped && patch.nodeIds !== undefined) patch.nodeIds = [sourceNodeId];
|
|
473
557
|
const workItem = manager.updateWorkItem(id, patch, opts);
|
|
@@ -475,29 +559,71 @@ export function applyAxInteraction(
|
|
|
475
559
|
return accept(type, sourceNodeId, workItem, 'ax-state-changed', { workItem });
|
|
476
560
|
}
|
|
477
561
|
case 'ax.evidence.add': {
|
|
478
|
-
const p = payloadParsed.data as {
|
|
562
|
+
const p = payloadParsed.data as {
|
|
563
|
+
kind: PmxAxEvidenceKind;
|
|
564
|
+
title: string;
|
|
565
|
+
body?: string | null;
|
|
566
|
+
ref?: string | null;
|
|
567
|
+
nodeIds?: string[];
|
|
568
|
+
data?: Record<string, unknown> | null;
|
|
569
|
+
};
|
|
479
570
|
const evidence = manager.addEvidence(
|
|
480
|
-
{
|
|
571
|
+
{
|
|
572
|
+
kind: p.kind,
|
|
573
|
+
title: p.title,
|
|
574
|
+
body: p.body ?? null,
|
|
575
|
+
ref: p.ref ?? null,
|
|
576
|
+
nodeIds: scopedNodeIds(p.nodeIds),
|
|
577
|
+
data: p.data ?? null,
|
|
578
|
+
},
|
|
481
579
|
opts,
|
|
482
580
|
);
|
|
483
581
|
return accept(type, sourceNodeId, evidence, 'ax-event-created', { evidence });
|
|
484
582
|
}
|
|
485
583
|
case 'ax.approval.request': {
|
|
486
|
-
const p = payloadParsed.data as {
|
|
584
|
+
const p = payloadParsed.data as {
|
|
585
|
+
title: string;
|
|
586
|
+
detail?: string | null;
|
|
587
|
+
action?: string | null;
|
|
588
|
+
nodeIds?: string[];
|
|
589
|
+
};
|
|
487
590
|
const approvalGate = manager.requestApproval(
|
|
488
|
-
{
|
|
591
|
+
{
|
|
592
|
+
title: p.title,
|
|
593
|
+
...(p.detail !== undefined ? { detail: p.detail } : {}),
|
|
594
|
+
...(p.action !== undefined ? { action: p.action } : {}),
|
|
595
|
+
nodeIds: scopedNodeIds(p.nodeIds),
|
|
596
|
+
},
|
|
489
597
|
opts,
|
|
490
598
|
);
|
|
491
599
|
return accept(type, sourceNodeId, approvalGate, 'ax-state-changed', { approvalGate });
|
|
492
600
|
}
|
|
493
601
|
case 'ax.approval.resolve': {
|
|
494
602
|
const p = payloadParsed.data as { id: string; decision: 'approved' | 'rejected'; resolution?: string };
|
|
495
|
-
const approvalGate = manager.resolveApproval(p.id, p.decision, {
|
|
496
|
-
|
|
603
|
+
const approvalGate = manager.resolveApproval(p.id, p.decision, {
|
|
604
|
+
...(p.resolution !== undefined ? { resolution: p.resolution } : {}),
|
|
605
|
+
source,
|
|
606
|
+
});
|
|
607
|
+
if (!approvalGate)
|
|
608
|
+
return reject(
|
|
609
|
+
type,
|
|
610
|
+
sourceNodeId,
|
|
611
|
+
404,
|
|
612
|
+
'approval-not-found',
|
|
613
|
+
`Approval "${p.id}" not found or already resolved.`,
|
|
614
|
+
);
|
|
497
615
|
return accept(type, sourceNodeId, approvalGate, 'ax-state-changed', { approvalGate });
|
|
498
616
|
}
|
|
499
617
|
case 'ax.review.add': {
|
|
500
|
-
const p = payloadParsed.data as {
|
|
618
|
+
const p = payloadParsed.data as {
|
|
619
|
+
body: string;
|
|
620
|
+
kind?: PmxAxReviewKind;
|
|
621
|
+
severity?: PmxAxReviewSeverity;
|
|
622
|
+
anchorType?: PmxAxReviewAnchorType;
|
|
623
|
+
nodeId?: string;
|
|
624
|
+
file?: string;
|
|
625
|
+
author?: string;
|
|
626
|
+
};
|
|
501
627
|
// Sandboxed surfaces may only review their own node; trusted surfaces may
|
|
502
628
|
// anchor to a file/region or another node.
|
|
503
629
|
// A node-interaction review carries a sourceNodeId, so it defaults to a node
|
|
@@ -510,13 +636,20 @@ export function applyAxInteraction(
|
|
|
510
636
|
...(p.kind ? { kind: p.kind } : {}),
|
|
511
637
|
...(p.severity ? { severity: p.severity } : {}),
|
|
512
638
|
anchorType,
|
|
513
|
-
nodeId: scoped ? sourceNodeId :
|
|
639
|
+
nodeId: scoped ? sourceNodeId : anchorType === 'node' ? (p.nodeId ?? sourceNodeId) : (p.nodeId ?? null),
|
|
514
640
|
...(!scoped && p.file !== undefined ? { file: p.file } : {}),
|
|
515
641
|
...(p.author !== undefined ? { author: p.author } : {}),
|
|
516
642
|
},
|
|
517
643
|
opts,
|
|
518
644
|
);
|
|
519
|
-
if (!reviewAnnotation)
|
|
645
|
+
if (!reviewAnnotation)
|
|
646
|
+
return reject(
|
|
647
|
+
type,
|
|
648
|
+
sourceNodeId,
|
|
649
|
+
400,
|
|
650
|
+
'invalid-review-anchor',
|
|
651
|
+
'Node-anchored review requires a nodeId that exists on the canvas.',
|
|
652
|
+
);
|
|
520
653
|
return accept(type, sourceNodeId, reviewAnnotation, 'ax-state-changed', { reviewAnnotation });
|
|
521
654
|
}
|
|
522
655
|
case 'ax.focus.set': {
|