pmx-canvas 0.7.3 → 0.8.0
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/.github/extensions/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
- package/.github/extensions/pmx-canvas/extension.mjs +25 -172
- package/.github/extensions/pmx-canvas/mcp-bootstrap.ts +198 -0
- package/.github/extensions/pmx-canvas/runtime.d.mts +41 -0
- package/.github/extensions/pmx-canvas/runtime.json +3 -0
- package/.github/extensions/pmx-canvas/runtime.mjs +194 -0
- package/.github/extensions/pmx-canvas/start-mcp.mjs +51 -0
- package/.github/extensions/pmx-canvas/tools.json +2560 -0
- package/CHANGELOG.md +47 -0
- package/Readme.md +21 -7
- package/dist/canvas/fonts/OFL.txt +93 -0
- package/dist/canvas/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-ext-400-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-ext-500-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-ext-600-normal.woff2 +0 -0
- package/dist/canvas/global.css +723 -672
- package/dist/canvas/index.js +90 -88
- package/dist/canvas/mcp-app.html +7878 -0
- package/dist/canvas/mermaid-entry.js +1 -1
- package/dist/canvas/surface-theme.css +151 -46
- package/dist/json-render/index.css +1 -1
- package/dist/types/cli/commands/record.d.ts +1 -1
- package/dist/types/client/canvas/NodeContextMark.d.ts +24 -0
- package/dist/types/client/canvas/SessionReceipt.d.ts +3 -1
- package/dist/types/client/icons.d.ts +1 -2
- package/dist/types/client/mcp-app/index.d.ts +1 -0
- package/dist/types/client/nodes/ax-node-actions.d.ts +2 -2
- package/dist/types/client/nodes/markdown-sanitize.d.ts +2 -0
- package/dist/types/client/nodes/use-workbench-asset.d.ts +2 -0
- package/dist/types/client/state/attention-store.d.ts +0 -5
- package/dist/types/client/state/context-status-store.d.ts +53 -0
- package/dist/types/client/state/intent-bridge.d.ts +3 -0
- package/dist/types/client/state/near-pin-store.d.ts +16 -0
- package/dist/types/client/state/presence-store.d.ts +5 -0
- package/dist/types/client/state/session-store.d.ts +33 -3
- package/dist/types/client/state/workbench-transport.d.ts +16 -0
- package/dist/types/client/theme/tokens.d.ts +1 -3
- package/dist/types/client/types.d.ts +9 -0
- package/dist/types/json-render/server.d.ts +1 -1
- package/dist/types/mcp/app-server.d.ts +3 -0
- package/dist/types/mcp/canvas-access.d.ts +1 -1
- package/dist/types/mcp/canvas-app.d.ts +4 -0
- package/dist/types/mcp/workbench-app.d.ts +4 -0
- package/dist/types/server/agent-presence.d.ts +1 -1
- package/dist/types/server/ax-state.d.ts +1 -1
- package/dist/types/server/canvas-db.d.ts +8 -0
- package/dist/types/server/canvas-geometry.d.ts +25 -0
- package/dist/types/server/canvas-operations.d.ts +1 -11
- package/dist/types/server/canvas-state.d.ts +25 -3
- package/dist/types/server/context-brief.d.ts +8 -1
- package/dist/types/server/context-reads.d.ts +30 -7
- package/dist/types/server/index.d.ts +6 -1
- package/dist/types/server/mcp-app-runtime.d.ts +1 -0
- package/dist/types/server/operations/http.d.ts +2 -0
- package/dist/types/server/operations/invoker.d.ts +4 -2
- package/dist/types/server/operations/ops/nodes.d.ts +0 -6
- package/dist/types/server/server.d.ts +1 -0
- package/dist/types/server/spatial-analysis.d.ts +2 -0
- package/dist/types/shared/agent-context.d.ts +22 -0
- package/dist/types/shared/agent-presence.d.ts +3 -10
- package/dist/types/shared/canvas-app.d.ts +54 -0
- package/dist/types/shared/themes.d.ts +1 -1
- package/docs/RELEASE.md +7 -3
- package/docs/agent-plugin.md +123 -0
- package/docs/ax-state-contract.md +40 -0
- package/docs/cli.md +7 -2
- package/docs/design/AgentContext.dc.html +186 -0
- package/docs/design/Before.dc.html +208 -0
- package/docs/design/BoardPins.dc.html +151 -0
- package/docs/design/Chrome.dc.html +208 -0
- package/docs/design/Context.dc.html +239 -0
- package/docs/design/Flows.dc.html +181 -0
- package/docs/design/Home.dc.html +209 -0
- package/docs/design/HomeGraph.dc.html +211 -0
- package/docs/design/HomeMap.dc.html +117 -0
- package/docs/design/HomeTeam.dc.html +176 -0
- package/docs/design/Light.dc.html +28 -0
- package/docs/design/Main.dc.html +280 -0
- package/docs/design/NearPin.dc.html +260 -0
- package/docs/design/Pane600.dc.html +120 -0
- package/docs/design/Relations.dc.html +297 -0
- package/docs/design/Themes.dc.html +121 -0
- package/docs/design/TokenSystem.dc.html +136 -0
- package/docs/design/Tokens.dc.html +163 -0
- package/docs/design/Types1.dc.html +177 -0
- package/docs/design/Types2.dc.html +226 -0
- package/docs/design/Types3.dc.html +195 -0
- package/docs/design/Uses.dc.html +152 -0
- package/docs/design/Vision.dc.html +116 -0
- package/docs/design/Zoom.dc.html +82 -0
- package/docs/design/canvas.json +274 -0
- package/docs/design.md +214 -0
- package/docs/environment.md +1 -1
- package/docs/http-api.md +80 -15
- package/docs/mcp.md +198 -3
- package/docs/node-types.md +34 -10
- package/docs/plans/plan-009-tech-debt-backlog.md +1 -1
- package/docs/plans/plan-015-design-foundation-wave-0.md +177 -0
- package/docs/plans/plan-016-context-made-visible-wave-1.md +148 -0
- package/docs/product-vision-2026-09.md +47 -7
- package/docs/screenshot.png +0 -0
- package/docs/sdk.md +1 -0
- package/package.json +10 -4
- package/plugins/pmx-canvas/LICENSE +21 -0
- package/plugins/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
- package/plugins/pmx-canvas/assets/preview.png +0 -0
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +930 -0
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +3 -0
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.mjs +194 -0
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/steering-delivery.mjs +79 -0
- package/plugins/pmx-canvas/mcp.json +12 -0
- package/plugins/pmx-canvas/plugin.json +24 -0
- package/plugins/pmx-canvas/runtime.json +3 -0
- package/plugins/pmx-canvas/runtime.mjs +194 -0
- package/plugins/pmx-canvas/start-mcp.mjs +51 -0
- package/plugins/pmx-canvas/tools.json +2560 -0
- package/skills/pmx-canvas/SKILL.md +10 -7
- package/skills/pmx-canvas/references/full-reference.md +11 -7
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +20 -8
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
- package/skills/pmx-canvas-testing/SKILL.md +5 -4
- package/src/cli/commands/copilot.ts +5 -0
- package/src/cli/commands/view.ts +1 -1
- package/src/cli/index.ts +8 -3
- package/src/client/App.tsx +6 -1
- package/src/client/canvas/AgentPresenceLayer.tsx +3 -3
- package/src/client/canvas/AnnotationLayer.tsx +1 -1
- package/src/client/canvas/CanvasNode.tsx +32 -49
- package/src/client/canvas/CanvasViewport.tsx +0 -2
- package/src/client/canvas/CommandPalette.tsx +3 -2
- package/src/client/canvas/ContextMenu.tsx +11 -0
- package/src/client/canvas/EdgeLayer.tsx +1 -1
- package/src/client/canvas/ExportDialog.tsx +33 -6
- package/src/client/canvas/IntentLayer.tsx +2 -2
- package/src/client/canvas/Minimap.tsx +12 -7
- package/src/client/canvas/NodeContextMark.tsx +156 -0
- package/src/client/canvas/SessionPanel.tsx +4 -7
- package/src/client/canvas/SessionReceipt.tsx +114 -17
- package/src/client/canvas/ShortcutOverlay.tsx +6 -1
- package/src/client/canvas/SnapshotPanel.tsx +11 -2
- package/src/client/canvas/ToolRail.tsx +12 -9
- package/src/client/canvas/TopBar.tsx +16 -11
- package/src/client/icons.tsx +2 -12
- package/src/client/mcp-app/index.tsx +137 -0
- package/src/client/mcp-app/style.css +10 -0
- package/src/client/nodes/AttachmentNode.tsx +8 -1
- package/src/client/nodes/AxStepControls.tsx +1 -1
- package/src/client/nodes/ContextNode.tsx +9 -9
- package/src/client/nodes/ExtAppFrame.tsx +13 -8
- package/src/client/nodes/FileNode.tsx +8 -5
- package/src/client/nodes/ImageNode.tsx +4 -2
- package/src/client/nodes/InlineMarkdownEditor.tsx +2 -1
- package/src/client/nodes/LedgerNode.tsx +2 -2
- package/src/client/nodes/MarkdownNode.tsx +44 -9
- package/src/client/nodes/McpAppNode.tsx +6 -4
- package/src/client/nodes/PromptNode.tsx +5 -17
- package/src/client/nodes/ResponseNode.tsx +3 -15
- package/src/client/nodes/StatusNode.tsx +2 -2
- package/src/client/nodes/StatusSummary.tsx +3 -1
- package/src/client/nodes/TraceNode.tsx +6 -4
- package/src/client/nodes/WebpageNode.tsx +1 -1
- package/src/client/nodes/ax-node-actions.ts +2 -2
- package/src/client/nodes/iframe-document-url.ts +3 -2
- package/src/client/nodes/markdown-sanitize.ts +10 -0
- package/src/client/nodes/surface-url.ts +10 -0
- package/src/client/nodes/use-surface-frame.ts +3 -1
- package/src/client/nodes/use-workbench-asset.ts +26 -0
- package/src/client/state/attention-bridge.ts +0 -8
- package/src/client/state/attention-store.ts +0 -17
- package/src/client/state/canvas-store.ts +4 -2
- package/src/client/state/context-status-store.ts +66 -0
- package/src/client/state/human-store.ts +2 -0
- package/src/client/state/iframe-mode.ts +5 -0
- package/src/client/state/intent-bridge.ts +10 -4
- package/src/client/state/near-pin-store.ts +53 -0
- package/src/client/state/presence-store.ts +5 -10
- package/src/client/state/session-store.ts +106 -9
- package/src/client/state/sse-bridge.ts +26 -7
- package/src/client/state/theme-override.ts +1 -1
- package/src/client/state/workbench-transport.ts +36 -0
- package/src/client/theme/global.css +723 -672
- package/src/client/theme/surface-theme.css +151 -46
- package/src/client/theme/tokens.ts +7 -11
- package/src/client/types.ts +10 -0
- package/src/json-render/server.ts +71 -14
- package/src/mcp/app-server.ts +64 -0
- package/src/mcp/canvas-access.ts +14 -11
- package/src/mcp/canvas-app.ts +318 -0
- package/src/mcp/server.ts +1 -1
- package/src/mcp/workbench-app.ts +211 -0
- package/src/server/agent-presence.ts +45 -6
- package/src/server/ax-context.ts +1 -1
- package/src/server/ax-state.ts +1 -1
- package/src/server/board-export.ts +5 -2
- package/src/server/canvas-db.ts +46 -12
- package/src/server/canvas-geometry.ts +64 -0
- package/src/server/canvas-operations.ts +6 -24
- package/src/server/canvas-state.ts +105 -15
- package/src/server/canvas-validation.ts +19 -4
- package/src/server/context-brief.ts +39 -2
- package/src/server/context-reads.ts +86 -15
- package/src/server/demo-state.json +31 -31
- package/src/server/html-surface.ts +4 -5
- package/src/server/index.ts +19 -3
- package/src/server/mcp-app-runtime.ts +11 -0
- package/src/server/operations/composites.ts +4 -3
- package/src/server/operations/http.ts +4 -4
- package/src/server/operations/invoker.ts +13 -4
- package/src/server/operations/ops/ax-read.ts +1 -1
- package/src/server/operations/ops/ax-timeline.ts +37 -0
- package/src/server/operations/ops/boards.ts +1 -1
- package/src/server/operations/ops/canvas-wire.ts +3 -6
- package/src/server/operations/ops/intent.ts +31 -2
- package/src/server/operations/ops/json-render.ts +1 -1
- package/src/server/operations/ops/nodes.ts +8 -76
- package/src/server/operations/ops/query.ts +8 -2
- package/src/server/operations/ops/snapshots.ts +4 -1
- package/src/server/operations/ops/viewport.ts +29 -5
- package/src/server/operations/registry.ts +38 -4
- package/src/server/scope-fence.ts +55 -10
- package/src/server/server.ts +83 -5
- package/src/server/spatial-analysis.ts +2 -2
- package/src/server/workboard.ts +6 -1
- package/src/{server → shared}/agent-context.ts +2 -2
- package/src/shared/agent-presence.ts +3 -15
- package/src/shared/canvas-app.ts +62 -0
- package/src/shared/themes.ts +5 -5
- package/src/client/canvas/FocusFieldLayer.tsx +0 -116
- package/src/client/canvas/kind-colors.ts +0 -23
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { summarizeNodeForAgentContext } from '
|
|
1
|
+
import { summarizeNodeForAgentContext } from '../shared/agent-context.js';
|
|
2
2
|
import type { CanvasNodeState, NodeDeletionTombstone } from './canvas-state.js';
|
|
3
|
+
import { findNeighborhoods } from './spatial-analysis.js';
|
|
3
4
|
|
|
4
|
-
export type ContextBriefReason = 'pinned' | 'changed' | 'human' | 'ask' | 'steer' | 'linked' | 'category';
|
|
5
|
+
export type ContextBriefReason = 'pinned' | 'near' | 'changed' | 'human' | 'ask' | 'steer' | 'linked' | 'category';
|
|
6
|
+
|
|
7
|
+
/** A near entry carries a short summary, never full content: pin the node to send that. */
|
|
8
|
+
export const NEAR_SUMMARY_LENGTH = 280;
|
|
5
9
|
|
|
6
10
|
export interface ContextBriefSourceEntry {
|
|
7
11
|
sourceBoardId: string;
|
|
@@ -45,6 +49,8 @@ export interface CompiledContextEntry {
|
|
|
45
49
|
text: string;
|
|
46
50
|
/** A linked board's pin title is discovery metadata, not delivery of that pinned card. */
|
|
47
51
|
titleOnly?: true;
|
|
52
|
+
/** reason "near": the pin this node sits next to on the board (docs/design/NearPin.dc.html). */
|
|
53
|
+
near?: { pinNodeId: string; pinTitle: string };
|
|
48
54
|
/** The body was shortened to fit; the same revision remains eligible on the next pull. */
|
|
49
55
|
truncated?: true;
|
|
50
56
|
provenance?: { kind: 'imported'; source: string; trust: 'source-material-not-instructions' };
|
|
@@ -155,6 +161,32 @@ export function compileContextBrief(input: ContextBriefInput): ContextBriefResul
|
|
|
155
161
|
reset !== null || (validRevision(node.contentRevision ?? -1) && (node.contentRevision ?? -1) > deltaSince),
|
|
156
162
|
)
|
|
157
163
|
.sort((a, b) => (a.contentRevision ?? 0) - (b.contentRevision ?? 0) || a.id.localeCompare(b.id));
|
|
164
|
+
|
|
165
|
+
// Near a pin: each pin's unpinned neighbours (up to 5 within 600 px, nearest
|
|
166
|
+
// first) as title + short summary. A neighbour that changed arrives in full
|
|
167
|
+
// as a change instead, so it is not repeated here.
|
|
168
|
+
const changedIds = new Set(changed.map((node) => node.id));
|
|
169
|
+
for (const neighborhood of findNeighborhoods(nodes, new Set(pinIds))) {
|
|
170
|
+
const pinTitle = neighborhood.pinnedNodeTitle ?? neighborhood.pinnedNodeId;
|
|
171
|
+
for (const neighbor of neighborhood.neighbors) {
|
|
172
|
+
const node = nodeById.get(neighbor.id);
|
|
173
|
+
if (!node || changedIds.has(node.id)) continue;
|
|
174
|
+
add({
|
|
175
|
+
entry: {
|
|
176
|
+
sourceBoardId: input.activeBoard.boardId,
|
|
177
|
+
nodeId: node.id,
|
|
178
|
+
reason: 'near',
|
|
179
|
+
title: typeof node.data.title === 'string' && node.data.title ? node.data.title : node.id,
|
|
180
|
+
text: summarizeNodeForAgentContext(node, {
|
|
181
|
+
defaultTextLength: NEAR_SUMMARY_LENGTH,
|
|
182
|
+
webpageTextLength: NEAR_SUMMARY_LENGTH,
|
|
183
|
+
}),
|
|
184
|
+
near: { pinNodeId: neighborhood.pinnedNodeId, pinTitle },
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
158
190
|
for (const node of changed) add(compileNode(node, input.activeBoard.boardId, 'changed', budget));
|
|
159
191
|
|
|
160
192
|
for (const entry of [...(input.entries ?? [])].sort((a, b) => {
|
|
@@ -285,6 +317,11 @@ export function compileContextBrief(input: ContextBriefInput): ContextBriefResul
|
|
|
285
317
|
if (partial) delivered.push(partial);
|
|
286
318
|
}
|
|
287
319
|
}
|
|
320
|
+
// Neighbours come right after the pins they sit next to; each is whole or absent.
|
|
321
|
+
for (const candidate of candidates.filter((item) => !reset && item.entry.reason === 'near')) {
|
|
322
|
+
const proposed = [...delivered, candidate.entry];
|
|
323
|
+
if (fits(makeDocument(proposed))) delivered.push(candidate.entry);
|
|
324
|
+
}
|
|
288
325
|
if (reset && !deltaBlocked) {
|
|
289
326
|
const resetEntries = candidates
|
|
290
327
|
.filter((item) => item.replacement && !delivered.includes(item.entry))
|
|
@@ -37,16 +37,32 @@ export interface ContextRead {
|
|
|
37
37
|
agentId: string | null;
|
|
38
38
|
pinnedNodeIds: string[];
|
|
39
39
|
deliveredNodeIds: string[];
|
|
40
|
+
/** Every node on the read board whose content was delivered, with its content revision at read time. */
|
|
41
|
+
readNodes: Record<string, number>;
|
|
40
42
|
bytes: number;
|
|
41
43
|
/** The board open when the read happened (null on Home). */
|
|
42
44
|
boardId: string | null;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
export type ContextReadInput = Omit<ContextRead, 'seq' | 'id' | 'at' | 'boardId'> & {
|
|
47
|
+
export type ContextReadInput = Omit<ContextRead, 'seq' | 'id' | 'at' | 'boardId' | 'readNodes'> & {
|
|
46
48
|
/** Explicit read target, captured before asynchronous formatting/proxy work. */
|
|
47
49
|
boardId?: string | null;
|
|
50
|
+
/** Ids of every serialized node in what the reader received; the server keeps those on the board. */
|
|
51
|
+
readNodeIds?: string[];
|
|
48
52
|
};
|
|
49
53
|
|
|
54
|
+
/** One node's read state on a board: the latest agent read that delivered its content. */
|
|
55
|
+
export interface NodeReadStatus {
|
|
56
|
+
nodeId: string;
|
|
57
|
+
lastReadAt: string;
|
|
58
|
+
/** Who read it: consumer, else agent id, else transport label. */
|
|
59
|
+
lastReadBy: string;
|
|
60
|
+
/** The node's content revision in that read; newer current revision means "changed since read". */
|
|
61
|
+
readRevision: number;
|
|
62
|
+
/** How many retained agent reads delivered its content. */
|
|
63
|
+
readCount: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
50
66
|
export interface ContextReadConsumerSummary {
|
|
51
67
|
consumer: string;
|
|
52
68
|
reads: number;
|
|
@@ -57,12 +73,13 @@ export interface ContextReadConsumerSummary {
|
|
|
57
73
|
}
|
|
58
74
|
|
|
59
75
|
/**
|
|
60
|
-
*
|
|
61
|
-
* what the reader received. A bare id list, a
|
|
62
|
-
* not count — the agent got the
|
|
76
|
+
* Ids of every serialized node (an object carrying `"id": "<id>"`, or a
|
|
77
|
+
* `nodeId` with its text) in what the reader received. A bare id list, a
|
|
78
|
+
* title, or a clipped-off node does not count — the agent got the node's
|
|
79
|
+
* name, not its content. Non-node ids (edges, intents) are dropped by the
|
|
80
|
+
* caller against the board's nodes.
|
|
63
81
|
*/
|
|
64
|
-
export function
|
|
65
|
-
const pinned = new Set(pinnedNodeIds);
|
|
82
|
+
export function deliveredIds(payloadText: string): Set<string> {
|
|
66
83
|
const delivered = new Set<string>();
|
|
67
84
|
const objectStarts: number[] = [];
|
|
68
85
|
let inString = false;
|
|
@@ -89,11 +106,10 @@ export function deliveredPinnedIds(pinnedNodeIds: string[], payloadText: string)
|
|
|
89
106
|
typeof value === 'object' &&
|
|
90
107
|
value !== null &&
|
|
91
108
|
!Array.isArray(value) &&
|
|
92
|
-
(
|
|
109
|
+
(typeof (value as { id?: unknown }).id === 'string' ||
|
|
93
110
|
(typeof (value as { nodeId?: unknown }).nodeId === 'string' &&
|
|
94
111
|
(value as { titleOnly?: unknown }).titleOnly !== true &&
|
|
95
|
-
typeof (value as { text?: unknown }).text === 'string'
|
|
96
|
-
pinned.has((value as { nodeId: string }).nodeId)))
|
|
112
|
+
typeof (value as { text?: unknown }).text === 'string'))
|
|
97
113
|
) {
|
|
98
114
|
delivered.add(
|
|
99
115
|
typeof (value as { id?: unknown }).id === 'string'
|
|
@@ -107,17 +123,25 @@ export function deliveredPinnedIds(pinnedNodeIds: string[], payloadText: string)
|
|
|
107
123
|
}
|
|
108
124
|
}
|
|
109
125
|
|
|
126
|
+
return delivered;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Pinned nodes whose content is in what the reader received (see `deliveredIds`). */
|
|
130
|
+
export function deliveredPinnedIds(pinnedNodeIds: string[], payloadText: string): string[] {
|
|
131
|
+
const delivered = deliveredIds(payloadText);
|
|
110
132
|
return pinnedNodeIds.filter((id) => delivered.has(id));
|
|
111
133
|
}
|
|
112
134
|
|
|
113
135
|
export function contextReadFromPayload(
|
|
114
|
-
base: Omit<ContextReadInput, 'deliveredNodeIds' | 'bytes'>,
|
|
136
|
+
base: Omit<ContextReadInput, 'deliveredNodeIds' | 'bytes' | 'readNodeIds'>,
|
|
115
137
|
payload: unknown,
|
|
116
138
|
): ContextReadInput {
|
|
117
139
|
const text = typeof payload === 'string' ? payload : (JSON.stringify(payload) ?? '');
|
|
140
|
+
const delivered = deliveredIds(text);
|
|
118
141
|
return {
|
|
119
142
|
...base,
|
|
120
|
-
deliveredNodeIds:
|
|
143
|
+
deliveredNodeIds: base.pinnedNodeIds.filter((id) => delivered.has(id)),
|
|
144
|
+
readNodeIds: [...delivered],
|
|
121
145
|
bytes: Buffer.byteLength(text, 'utf-8'),
|
|
122
146
|
};
|
|
123
147
|
}
|
|
@@ -135,15 +159,21 @@ export const CONTEXT_READS_SCHEMA_SQL = `
|
|
|
135
159
|
pinned_node_ids TEXT NOT NULL DEFAULT '[]',
|
|
136
160
|
delivered_node_ids TEXT NOT NULL DEFAULT '[]',
|
|
137
161
|
bytes INTEGER NOT NULL DEFAULT 0,
|
|
138
|
-
board_id TEXT
|
|
162
|
+
board_id TEXT,
|
|
163
|
+
read_nodes TEXT NOT NULL DEFAULT '{}'
|
|
139
164
|
);
|
|
140
165
|
`;
|
|
141
166
|
|
|
142
|
-
export function appendContextReadToDB(
|
|
167
|
+
export function appendContextReadToDB(
|
|
168
|
+
db: Database,
|
|
169
|
+
input: ContextReadInput,
|
|
170
|
+
boardId: string | null,
|
|
171
|
+
readNodes: Record<string, number> = {},
|
|
172
|
+
): ContextRead {
|
|
143
173
|
const id = `read-${randomUUID()}`;
|
|
144
174
|
const at = new Date().toISOString();
|
|
145
175
|
db.run(
|
|
146
|
-
'INSERT INTO context_reads (id, at, channel, resource, source, consumer, agent_id, pinned_node_ids, delivered_node_ids, bytes, board_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
176
|
+
'INSERT INTO context_reads (id, at, channel, resource, source, consumer, agent_id, pinned_node_ids, delivered_node_ids, bytes, board_id, read_nodes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
147
177
|
[
|
|
148
178
|
id,
|
|
149
179
|
at,
|
|
@@ -156,11 +186,13 @@ export function appendContextReadToDB(db: Database, input: ContextReadInput, boa
|
|
|
156
186
|
JSON.stringify(input.deliveredNodeIds),
|
|
157
187
|
input.bytes,
|
|
158
188
|
boardId,
|
|
189
|
+
JSON.stringify(readNodes),
|
|
159
190
|
],
|
|
160
191
|
);
|
|
161
192
|
const seq = Number(db.query<{ seq: number }, []>('SELECT last_insert_rowid() AS seq').get()?.seq ?? 0);
|
|
162
193
|
db.run('DELETE FROM context_reads WHERE seq <= ?', [seq - CONTEXT_READ_RETENTION]);
|
|
163
|
-
|
|
194
|
+
const { readNodeIds: _readNodeIds, ...record } = input;
|
|
195
|
+
return { ...record, seq, id, at, boardId, readNodes };
|
|
164
196
|
}
|
|
165
197
|
|
|
166
198
|
interface ContextReadRow {
|
|
@@ -176,6 +208,7 @@ interface ContextReadRow {
|
|
|
176
208
|
delivered_node_ids: string;
|
|
177
209
|
bytes: number;
|
|
178
210
|
board_id: string | null;
|
|
211
|
+
read_nodes: string;
|
|
179
212
|
}
|
|
180
213
|
|
|
181
214
|
function rowToContextRead(row: ContextReadRow): ContextRead {
|
|
@@ -190,11 +223,49 @@ function rowToContextRead(row: ContextReadRow): ContextRead {
|
|
|
190
223
|
agentId: row.agent_id,
|
|
191
224
|
pinnedNodeIds: JSON.parse(row.pinned_node_ids) as string[],
|
|
192
225
|
deliveredNodeIds: JSON.parse(row.delivered_node_ids) as string[],
|
|
226
|
+
readNodes: JSON.parse(row.read_nodes) as Record<string, number>,
|
|
193
227
|
bytes: row.bytes,
|
|
194
228
|
boardId: row.board_id,
|
|
195
229
|
};
|
|
196
230
|
}
|
|
197
231
|
|
|
232
|
+
/** Per node on a board, the latest agent read that delivered its content (newest row wins). */
|
|
233
|
+
export function loadNodeReadStatusFromDB(db: Database, boardId: string): NodeReadStatus[] {
|
|
234
|
+
const rows = db
|
|
235
|
+
.query<ContextReadRow, [string]>('SELECT * FROM context_reads WHERE board_id = ? ORDER BY seq DESC')
|
|
236
|
+
.all(boardId);
|
|
237
|
+
const status = new Map<string, NodeReadStatus>();
|
|
238
|
+
for (const read of rows.map(rowToContextRead)) {
|
|
239
|
+
for (const [nodeId, readRevision] of Object.entries(read.readNodes)) {
|
|
240
|
+
const known = status.get(nodeId);
|
|
241
|
+
if (known) {
|
|
242
|
+
known.readCount += 1;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
status.set(nodeId, {
|
|
246
|
+
nodeId,
|
|
247
|
+
lastReadAt: read.at,
|
|
248
|
+
lastReadBy: read.consumer ?? read.agentId ?? read.source,
|
|
249
|
+
readRevision,
|
|
250
|
+
readCount: 1,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return [...status.values()];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Every node on a board whose content an agent read at or after `since` (ISO). */
|
|
258
|
+
export function loadReadNodeIdsSince(db: Database, boardId: string, since: string): string[] {
|
|
259
|
+
const rows = db
|
|
260
|
+
.query<{ read_nodes: string }, [string, string]>(
|
|
261
|
+
'SELECT read_nodes FROM context_reads WHERE board_id = ? AND at >= ? ORDER BY seq',
|
|
262
|
+
)
|
|
263
|
+
.all(boardId, since);
|
|
264
|
+
const ids = new Set<string>();
|
|
265
|
+
for (const row of rows) for (const id of Object.keys(JSON.parse(row.read_nodes) as object)) ids.add(id);
|
|
266
|
+
return [...ids];
|
|
267
|
+
}
|
|
268
|
+
|
|
198
269
|
/** Newest first. The summary covers every retained row, not just the returned page. */
|
|
199
270
|
export function loadContextReadsFromDB(
|
|
200
271
|
db: Database,
|
|
@@ -3134,7 +3134,7 @@
|
|
|
3134
3134
|
"actor": "agent",
|
|
3135
3135
|
"source": "api"
|
|
3136
3136
|
},
|
|
3137
|
-
"contentRevision":
|
|
3137
|
+
"contentRevision": 46,
|
|
3138
3138
|
"kind": "markdown",
|
|
3139
3139
|
"title": "Sprint 41 — canvas polish",
|
|
3140
3140
|
"content": "Three nodes, one frame. Moving the frame moves the children; ungrouping leaves them exactly where they are.\n\n- Group children keep their own positions by default\n- `childLayout` opts into auto-packing\n- The frame is stored as `data.children` on the group",
|
|
@@ -3174,7 +3174,7 @@
|
|
|
3174
3174
|
"actor": "agent",
|
|
3175
3175
|
"source": "api"
|
|
3176
3176
|
},
|
|
3177
|
-
"contentRevision":
|
|
3177
|
+
"contentRevision": 47,
|
|
3178
3178
|
"kind": "status",
|
|
3179
3179
|
"title": "Sprint state",
|
|
3180
3180
|
"content": null,
|
|
@@ -3215,7 +3215,7 @@
|
|
|
3215
3215
|
"actor": "agent",
|
|
3216
3216
|
"source": "api"
|
|
3217
3217
|
},
|
|
3218
|
-
"contentRevision":
|
|
3218
|
+
"contentRevision": 48,
|
|
3219
3219
|
"kind": "trace",
|
|
3220
3220
|
"title": "canvas_group",
|
|
3221
3221
|
"content": null,
|
|
@@ -3256,7 +3256,7 @@
|
|
|
3256
3256
|
"actor": "agent",
|
|
3257
3257
|
"source": "api"
|
|
3258
3258
|
},
|
|
3259
|
-
"contentRevision":
|
|
3259
|
+
"contentRevision": 45,
|
|
3260
3260
|
"kind": "group",
|
|
3261
3261
|
"title": "Sprint 41",
|
|
3262
3262
|
"content": null,
|
|
@@ -3292,7 +3292,7 @@
|
|
|
3292
3292
|
"actor": "agent",
|
|
3293
3293
|
"source": "api"
|
|
3294
3294
|
},
|
|
3295
|
-
"contentRevision":
|
|
3295
|
+
"contentRevision": 49,
|
|
3296
3296
|
"kind": "markdown",
|
|
3297
3297
|
"title": "⑦ HTML primitives",
|
|
3298
3298
|
"content": "All 21 generated communication surfaces — self-contained sandboxed documents, no build step.",
|
|
@@ -3377,7 +3377,7 @@
|
|
|
3377
3377
|
"actor": "agent",
|
|
3378
3378
|
"source": "api"
|
|
3379
3379
|
},
|
|
3380
|
-
"contentRevision":
|
|
3380
|
+
"contentRevision": 50,
|
|
3381
3381
|
"kind": "html",
|
|
3382
3382
|
"title": "How should the demo board be generated?",
|
|
3383
3383
|
"content": "Side-by-side options with tradeoffs, pros, cons, and code or evidence snippets.",
|
|
@@ -3487,7 +3487,7 @@
|
|
|
3487
3487
|
"actor": "agent",
|
|
3488
3488
|
"source": "api"
|
|
3489
3489
|
},
|
|
3490
|
-
"contentRevision":
|
|
3490
|
+
"contentRevision": 51,
|
|
3491
3491
|
"kind": "html",
|
|
3492
3492
|
"title": "Demo board rebuild",
|
|
3493
3493
|
"content": "Implementation plan with milestones, data flow, code checkpoints, and risks.",
|
|
@@ -3566,7 +3566,7 @@
|
|
|
3566
3566
|
"actor": "agent",
|
|
3567
3567
|
"source": "api"
|
|
3568
3568
|
},
|
|
3569
|
-
"contentRevision":
|
|
3569
|
+
"contentRevision": 52,
|
|
3570
3570
|
"kind": "html",
|
|
3571
3571
|
"title": "Review — auto-fit floor",
|
|
3572
3572
|
"content": "Annotated PR/code review sheet with severity-colored findings and diff excerpts.",
|
|
@@ -3654,7 +3654,7 @@
|
|
|
3654
3654
|
"actor": "agent",
|
|
3655
3655
|
"source": "api"
|
|
3656
3656
|
},
|
|
3657
|
-
"contentRevision":
|
|
3657
|
+
"contentRevision": 53,
|
|
3658
3658
|
"kind": "html",
|
|
3659
3659
|
"title": "PR — replace the demo board",
|
|
3660
3660
|
"content": "Reviewer-ready PR narrative with motivation, before/after, file tour, test plan, and rollout notes.",
|
|
@@ -3766,7 +3766,7 @@
|
|
|
3766
3766
|
"actor": "agent",
|
|
3767
3767
|
"source": "api"
|
|
3768
3768
|
},
|
|
3769
|
-
"contentRevision":
|
|
3769
|
+
"contentRevision": 54,
|
|
3770
3770
|
"kind": "html",
|
|
3771
3771
|
"title": "Canvas server map",
|
|
3772
3772
|
"content": "Module or architecture map with boxes, relationships, entry points, and hot paths.",
|
|
@@ -3895,7 +3895,7 @@
|
|
|
3895
3895
|
"actor": "agent",
|
|
3896
3896
|
"source": "api"
|
|
3897
3897
|
},
|
|
3898
|
-
"contentRevision":
|
|
3898
|
+
"contentRevision": 55,
|
|
3899
3899
|
"kind": "html",
|
|
3900
3900
|
"title": "Walkthrough — creating a node",
|
|
3901
3901
|
"content": "Guided code-path map with modules, ordered file steps, snippets, key files, and gotchas.",
|
|
@@ -3998,7 +3998,7 @@
|
|
|
3998
3998
|
"actor": "agent",
|
|
3999
3999
|
"source": "api"
|
|
4000
4000
|
},
|
|
4001
|
-
"contentRevision":
|
|
4001
|
+
"contentRevision": 56,
|
|
4002
4002
|
"kind": "html",
|
|
4003
4003
|
"title": "Board typography directions",
|
|
4004
4004
|
"content": "Visual design directions, tokens, swatches, type samples, and rationale.",
|
|
@@ -4081,7 +4081,7 @@
|
|
|
4081
4081
|
"actor": "agent",
|
|
4082
4082
|
"source": "api"
|
|
4083
4083
|
},
|
|
4084
|
-
"contentRevision":
|
|
4084
|
+
"contentRevision": 57,
|
|
4085
4085
|
"kind": "html",
|
|
4086
4086
|
"title": "Node titlebar states",
|
|
4087
4087
|
"content": "Contact sheet for component variants, states, sizes, and accessibility notes.",
|
|
@@ -4179,7 +4179,7 @@
|
|
|
4179
4179
|
"actor": "agent",
|
|
4180
4180
|
"source": "api"
|
|
4181
4181
|
},
|
|
4182
|
-
"contentRevision":
|
|
4182
|
+
"contentRevision": 58,
|
|
4183
4183
|
"kind": "html",
|
|
4184
4184
|
"title": "Node expand transition",
|
|
4185
4185
|
"content": "Throwaway interaction or motion sandbox with a live stage, controls, annotations, and copyable config.",
|
|
@@ -4265,7 +4265,7 @@
|
|
|
4265
4265
|
"actor": "agent",
|
|
4266
4266
|
"source": "api"
|
|
4267
4267
|
},
|
|
4268
|
-
"contentRevision":
|
|
4268
|
+
"contentRevision": 59,
|
|
4269
4269
|
"kind": "html",
|
|
4270
4270
|
"title": "Release pipeline",
|
|
4271
4271
|
"content": "Clickable flowchart for pipelines, user journeys, process diagrams, and failure paths.",
|
|
@@ -4360,7 +4360,7 @@
|
|
|
4360
4360
|
"actor": "agent",
|
|
4361
4361
|
"source": "api"
|
|
4362
4362
|
},
|
|
4363
|
-
"contentRevision":
|
|
4363
|
+
"contentRevision": 60,
|
|
4364
4364
|
"kind": "html",
|
|
4365
4365
|
"title": "Why a spatial canvas",
|
|
4366
4366
|
"content": "Arrow-key HTML deck with sections, speaker notes, and copyable JSON payload. Slides: Chat is a queue, work is a graph, The canvas is the agent’s working memory, Pinning is the steering wheel.",
|
|
@@ -4478,7 +4478,7 @@
|
|
|
4478
4478
|
"actor": "agent",
|
|
4479
4479
|
"source": "api"
|
|
4480
4480
|
},
|
|
4481
|
-
"contentRevision":
|
|
4481
|
+
"contentRevision": 61,
|
|
4482
4482
|
"kind": "html",
|
|
4483
4483
|
"title": "PMX Canvas 0.4.7 briefing",
|
|
4484
4484
|
"content": "PowerPoint-style fullscreen-ready HTML presentation with slide navigation, progress, speaker notes, and presentation metadata. Slides: Where we are, What changed in 0.4.7, Next.",
|
|
@@ -4643,7 +4643,7 @@
|
|
|
4643
4643
|
"actor": "agent",
|
|
4644
4644
|
"source": "api"
|
|
4645
4645
|
},
|
|
4646
|
-
"contentRevision":
|
|
4646
|
+
"contentRevision": 62,
|
|
4647
4647
|
"kind": "html",
|
|
4648
4648
|
"title": "Figures — the collaboration loop",
|
|
4649
4649
|
"content": "Inline SVG figure sheet with captions and per-figure SVG copy/export controls.",
|
|
@@ -4749,7 +4749,7 @@
|
|
|
4749
4749
|
"actor": "agent",
|
|
4750
4750
|
"source": "api"
|
|
4751
4751
|
},
|
|
4752
|
-
"contentRevision":
|
|
4752
|
+
"contentRevision": 63,
|
|
4753
4753
|
"kind": "html",
|
|
4754
4754
|
"title": "How context pins work",
|
|
4755
4755
|
"content": "Readable explainer with TLDR, collapsible steps, annotated snippets, FAQ, and glossary.",
|
|
@@ -4870,7 +4870,7 @@
|
|
|
4870
4870
|
"actor": "agent",
|
|
4871
4871
|
"source": "api"
|
|
4872
4872
|
},
|
|
4873
|
-
"contentRevision":
|
|
4873
|
+
"contentRevision": 64,
|
|
4874
4874
|
"kind": "html",
|
|
4875
4875
|
"title": "Blank ext-app tiles on WebKit",
|
|
4876
4876
|
"content": "Post-incident report with impact metrics, minute-by-minute timeline, root cause, log excerpts, and action checklist.",
|
|
@@ -4956,7 +4956,7 @@
|
|
|
4956
4956
|
"actor": "agent",
|
|
4957
4957
|
"source": "api"
|
|
4958
4958
|
},
|
|
4959
|
-
"contentRevision":
|
|
4959
|
+
"contentRevision": 65,
|
|
4960
4960
|
"kind": "html",
|
|
4961
4961
|
"title": "Weekly canvas status",
|
|
4962
4962
|
"content": "Skimmable report with metrics, shipped/slipped lists, blockers, and next actions.",
|
|
@@ -5042,7 +5042,7 @@
|
|
|
5042
5042
|
"actor": "agent",
|
|
5043
5043
|
"source": "api"
|
|
5044
5044
|
},
|
|
5045
|
-
"contentRevision":
|
|
5045
|
+
"contentRevision": 66,
|
|
5046
5046
|
"kind": "html",
|
|
5047
5047
|
"title": "Post-0.4.7 triage",
|
|
5048
5048
|
"content": "Draggable Now/Next/Later/Cut board with copy-as-markdown export.",
|
|
@@ -5134,7 +5134,7 @@
|
|
|
5134
5134
|
"actor": "agent",
|
|
5135
5135
|
"source": "api"
|
|
5136
5136
|
},
|
|
5137
|
-
"contentRevision":
|
|
5137
|
+
"contentRevision": 67,
|
|
5138
5138
|
"kind": "html",
|
|
5139
5139
|
"title": "Canvas feature flags",
|
|
5140
5140
|
"content": "Form-like editor for flags or structured config with dependency warnings and copy-diff export.",
|
|
@@ -5203,7 +5203,7 @@
|
|
|
5203
5203
|
"actor": "agent",
|
|
5204
5204
|
"source": "api"
|
|
5205
5205
|
},
|
|
5206
|
-
"contentRevision":
|
|
5206
|
+
"contentRevision": 68,
|
|
5207
5207
|
"kind": "html",
|
|
5208
5208
|
"title": "Board summary prompt",
|
|
5209
5209
|
"content": "Side-by-side prompt/template editor with live variable previews and copy export.",
|
|
@@ -5256,7 +5256,7 @@
|
|
|
5256
5256
|
"actor": "agent",
|
|
5257
5257
|
"source": "api"
|
|
5258
5258
|
},
|
|
5259
|
-
"contentRevision":
|
|
5259
|
+
"contentRevision": 69,
|
|
5260
5260
|
"kind": "html",
|
|
5261
5261
|
"title": "Agent board",
|
|
5262
5262
|
"content": "Live agent work board: hand the agent tasks, watch status, steer, and run bounded task loops.",
|
|
@@ -5337,7 +5337,7 @@
|
|
|
5337
5337
|
"actor": "agent",
|
|
5338
5338
|
"source": "api"
|
|
5339
5339
|
},
|
|
5340
|
-
"contentRevision":
|
|
5340
|
+
"contentRevision": 85,
|
|
5341
5341
|
"kind": "html",
|
|
5342
5342
|
"title": "Release flow",
|
|
5343
5343
|
"content": "HTML primitive: ax-flow\nLive task-flow control panel: a step diagram with an explicit loop-back, per-step status, and a one-click materialize onto the board.\nRelease flow PMX HTML primitive / ax-flow Release flow Live task-flow control panel: a step diagram with an explicit loop-back, per-step status, and a one-click materialize onto the board. Copy JSON Copy prompt AX bridge unavailable This flow is not running as an AX-enabled canvas node, so its controls are inert. Release flow Each step is a canvas AX work item. Materialize drops the same flow onto the board as real nodes. loops back to step 1 · max 3 runs Run the flow Queue every step A step is queued once — after that its status control advances it (todo → in-progress → done). or run the whole flow as a bounded loop: max runs Start loop Steer Queue steering message Queued for the agent's next turn — steering is recorded on the timeline, it does not wake a running agent. Loop run 0 of 0 Each run is its own work item. When you mark the current run done , the surface opens the next run...",
|
|
@@ -5373,7 +5373,7 @@
|
|
|
5373
5373
|
"actor": "agent",
|
|
5374
5374
|
"source": "api"
|
|
5375
5375
|
},
|
|
5376
|
-
"contentRevision":
|
|
5376
|
+
"contentRevision": 71,
|
|
5377
5377
|
"kind": "markdown",
|
|
5378
5378
|
"title": "⑧ Agent at work",
|
|
5379
5379
|
"content": "A task caught mid-flight: one work item per step, status chips on the nodes, controls that steer the agent.",
|
|
@@ -5454,7 +5454,7 @@
|
|
|
5454
5454
|
"actor": "agent",
|
|
5455
5455
|
"source": "api"
|
|
5456
5456
|
},
|
|
5457
|
-
"contentRevision":
|
|
5457
|
+
"contentRevision": 86,
|
|
5458
5458
|
"kind": "markdown",
|
|
5459
5459
|
"title": "1. Reproduce",
|
|
5460
5460
|
"content": "Ran the publish gate 20× in a loop. Fails 3 in 20, always on the registry probe.",
|
|
@@ -5500,7 +5500,7 @@
|
|
|
5500
5500
|
"actor": "agent",
|
|
5501
5501
|
"source": "api"
|
|
5502
5502
|
},
|
|
5503
|
-
"contentRevision":
|
|
5503
|
+
"contentRevision": 87,
|
|
5504
5504
|
"kind": "markdown",
|
|
5505
5505
|
"title": "2. Fix",
|
|
5506
5506
|
"content": "Retry the probe with backoff instead of failing the gate on the first timeout.",
|
|
@@ -5592,7 +5592,7 @@
|
|
|
5592
5592
|
"actor": "agent",
|
|
5593
5593
|
"source": "api"
|
|
5594
5594
|
},
|
|
5595
|
-
"contentRevision":
|
|
5595
|
+
"contentRevision": 83,
|
|
5596
5596
|
"kind": "markdown",
|
|
5597
5597
|
"title": "4. Ship",
|
|
5598
5598
|
"content": "Land the patch, then watch the next three releases.",
|
|
@@ -5628,7 +5628,7 @@
|
|
|
5628
5628
|
"actor": "agent",
|
|
5629
5629
|
"source": "api"
|
|
5630
5630
|
},
|
|
5631
|
-
"contentRevision":
|
|
5631
|
+
"contentRevision": 88,
|
|
5632
5632
|
"kind": "markdown",
|
|
5633
5633
|
"title": "How to read this band",
|
|
5634
5634
|
"content": "Each step is a real **AX work item**, so the chip on a step node is the\nagent’s live status — it moves as the agent works.\n\nThe buttons in each step footer steer it: **Start**, **Done** and\n**Blocked** write that step’s work item, which the agent reads back from\n`canvas://ax-work`.\n\nStep 2 is pinned, so `canvas://pinned-context` hands the agent the\nstep it is actually on.",
|
|
@@ -226,12 +226,11 @@ export function buildMermaidSurfaceHtml(
|
|
|
226
226
|
): string {
|
|
227
227
|
// srcdoc-rendered surfaces in hosts that block sub-frame subresources (the
|
|
228
228
|
// Claude Code desktop browser blocks the script request too, not just the
|
|
229
|
-
// frame document) get
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
// `<!--` script-data-escaped states and renders as page text).
|
|
229
|
+
// frame document) get an inline bootstrap. Assigning decoded script text
|
|
230
|
+
// avoids HTML parser escaped states (`<!--`, `</script>`) in the bundle,
|
|
231
|
+
// without requiring data: script URLs that MCP App hosts may prohibit.
|
|
233
232
|
const entry = inlineEntry
|
|
234
|
-
? `<script
|
|
233
|
+
? `<script>(()=>{const s=document.createElement('script');s.textContent=new TextDecoder().decode(Uint8Array.from(atob('${Buffer.from(inlineEntry, 'utf-8').toString('base64')}'),c=>c.charCodeAt(0)));document.head.appendChild(s)})()</script>`
|
|
235
234
|
: '<script src="/canvas/mermaid-entry.js"></script>';
|
|
236
235
|
return `<pre class="mermaid-source" data-fit="${fit}" data-frame-token="${sanitizeFrameToken(frameToken)}" style="display:none">${escapeSurfaceHtml(source)}</pre>${entry}`;
|
|
237
236
|
}
|
package/src/server/index.ts
CHANGED
|
@@ -218,6 +218,8 @@ export class PmxCanvas extends EventEmitter {
|
|
|
218
218
|
async start(options?: {
|
|
219
219
|
open?: boolean;
|
|
220
220
|
automationWebView?: boolean | CanvasAutomationWebViewOptions;
|
|
221
|
+
/** Embedded startup must not launch external MCP backends from saved nodes. */
|
|
222
|
+
allowExternalMcp?: boolean;
|
|
221
223
|
/**
|
|
222
224
|
* Bind a nearby free port when the preferred one is taken instead of
|
|
223
225
|
* failing. Default false (an explicit SDK port is honored exactly); the
|
|
@@ -225,7 +227,11 @@ export class PmxCanvas extends EventEmitter {
|
|
|
225
227
|
*/
|
|
226
228
|
allowPortFallback?: boolean;
|
|
227
229
|
}): Promise<void> {
|
|
228
|
-
const base = startCanvasServer({
|
|
230
|
+
const base = startCanvasServer({
|
|
231
|
+
port: this._port,
|
|
232
|
+
allowPortFallback: options?.allowPortFallback ?? false,
|
|
233
|
+
allowExternalMcp: options?.allowExternalMcp,
|
|
234
|
+
});
|
|
229
235
|
if (!base) {
|
|
230
236
|
throw new Error(`Failed to start canvas server on port ${this._port}`);
|
|
231
237
|
}
|
|
@@ -900,6 +906,11 @@ export class PmxCanvas extends EventEmitter {
|
|
|
900
906
|
return canvasState.getContextReads(limit);
|
|
901
907
|
}
|
|
902
908
|
|
|
909
|
+
/** Per node on a board: when an agent last read its content, who, and the content revision it read. */
|
|
910
|
+
getNodeReadStatus(board?: string): ReturnType<typeof canvasState.getNodeReadStatus> {
|
|
911
|
+
return canvasState.getNodeReadStatus(board ?? canvasState.activeBoardId);
|
|
912
|
+
}
|
|
913
|
+
|
|
903
914
|
listWorkItems(): PmxAxWorkItem[] {
|
|
904
915
|
return canvasState.getWorkItems();
|
|
905
916
|
}
|
|
@@ -1298,8 +1309,13 @@ export class PmxCanvas extends EventEmitter {
|
|
|
1298
1309
|
return applyCanvasNodeUpdates(updates);
|
|
1299
1310
|
}
|
|
1300
1311
|
|
|
1301
|
-
|
|
1302
|
-
|
|
1312
|
+
/** `reason` is kept with newly pinned nodes and shown to the human on hover. */
|
|
1313
|
+
setContextPins(
|
|
1314
|
+
nodeIds: string[],
|
|
1315
|
+
mode: 'set' | 'add' | 'remove' = 'set',
|
|
1316
|
+
reason?: string,
|
|
1317
|
+
): { count: number; nodeIds: string[] } {
|
|
1318
|
+
const result = setCanvasContextPins(nodeIds, mode, reason);
|
|
1303
1319
|
emitPrimaryWorkbenchEvent('context-pins-changed', { count: result.count, nodeIds: result.nodeIds });
|
|
1304
1320
|
return result;
|
|
1305
1321
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
1
2
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
3
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
4
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
@@ -18,6 +19,13 @@ import type { McpUiClientCapabilities, McpUiResourceCsp, McpUiResourceMeta } fro
|
|
|
18
19
|
import { getToolUiResourceUri } from '@modelcontextprotocol/ext-apps/app-bridge';
|
|
19
20
|
import { normalizeExtAppToolResult } from './ext-app-tool-result.js';
|
|
20
21
|
|
|
22
|
+
// A restriction follows async rehydration spawned by an embedded operation,
|
|
23
|
+
// without disabling independent requests from the local workbench.
|
|
24
|
+
const externalMcpDisabled = new AsyncLocalStorage<boolean>();
|
|
25
|
+
export function withoutExternalMcp<T>(run: () => T): T {
|
|
26
|
+
return externalMcpDisabled.run(true, run);
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
export interface ExternalMcpHttpTransportConfig {
|
|
22
30
|
type: 'http';
|
|
23
31
|
url: string;
|
|
@@ -210,6 +218,9 @@ async function createSession(
|
|
|
210
218
|
serverName?: string,
|
|
211
219
|
timeoutMs?: number,
|
|
212
220
|
): Promise<McpAppSession> {
|
|
221
|
+
if (externalMcpDisabled.getStore()) {
|
|
222
|
+
throw new Error('Open external MCP apps from the local PMX workbench; embedded actions cannot launch them.');
|
|
223
|
+
}
|
|
213
224
|
const transport = buildTransport(transportConfig);
|
|
214
225
|
const client = new Client({ name: 'pmx-canvas-app-host', version: '0.1.0' }, { capabilities: clientCapabilities });
|
|
215
226
|
await client.connect(transport, requestOptions(timeoutMs));
|
|
@@ -323,14 +323,15 @@ export const compositeToolDefinitions: CompositeToolDefinition[] = [
|
|
|
323
323
|
{
|
|
324
324
|
toolName: 'canvas_ax_timeline',
|
|
325
325
|
description:
|
|
326
|
-
'Read or write the bounded PMX AX timeline. Action "read" returns recent agent-events, evidence, and steering messages plus counts (limit); "record-event" records a normalized agent-event (kind prompt|assistant-message|tool-start|tool-result|failure|approval|steering, summary, detail, data); "add-evidence" records an evidence item (kind logs|tool-result|screenshot|file|diff|test-output, title, body, ref); "send-steering" records a steering message (message); "reads" returns the context read log — which canvas context each agent read and which pinned nodes reached it — with a per-consumer summary (limit). Timeline rows persist for diagnostics/continuity but are not restored by snapshots; exposed via canvas://ax-timeline.',
|
|
327
|
-
actionSummary: 'read | record-event | add-evidence | send-steering | reads',
|
|
326
|
+
'Read or write the bounded PMX AX timeline. Action "read" returns recent agent-events, evidence, and steering messages plus counts (limit); "record-event" records a normalized agent-event (kind prompt|assistant-message|tool-start|tool-result|failure|approval|steering, summary, detail, data); "add-evidence" records an evidence item (kind logs|tool-result|screenshot|file|diff|test-output, title, body, ref); "send-steering" records a steering message (message); "reads" returns the context read log — which canvas context each agent read and which pinned nodes reached it — with a per-consumer summary (limit); "read-status" returns, per node on a board, when an agent last read its content, who, and the content revision it read (board). Timeline rows persist for diagnostics/continuity but are not restored by snapshots; exposed via canvas://ax-timeline.',
|
|
327
|
+
actionSummary: 'read | record-event | add-evidence | send-steering | reads | read-status',
|
|
328
328
|
actions: {
|
|
329
329
|
read: 'ax.timeline.get',
|
|
330
330
|
'record-event': 'ax.event.record',
|
|
331
331
|
'add-evidence': 'ax.evidence.add',
|
|
332
332
|
'send-steering': 'ax.steer',
|
|
333
333
|
reads: 'ax.reads.list',
|
|
334
|
+
'read-status': 'ax.reads.status',
|
|
334
335
|
},
|
|
335
336
|
},
|
|
336
337
|
{
|
|
@@ -346,7 +347,7 @@ export const compositeToolDefinitions: CompositeToolDefinition[] = [
|
|
|
346
347
|
{
|
|
347
348
|
toolName: 'canvas_intent',
|
|
348
349
|
description:
|
|
349
|
-
'Ghost Cursor of Intent — announce the spatial move you are ABOUT to make so the canvas paints a faint pre-commit placeholder (legibility: the human sees the next move forming, and can veto it). Action "signal" registers an intent (kind create|move|connect|remove|edit; pass position for create
|
|
350
|
+
'Ghost Cursor of Intent — announce the spatial move you are ABOUT to make so the canvas paints a faint pre-commit placeholder (legibility: the human sees the next move forming, and can veto it). Action "signal" registers an intent (kind create|move|connect|remove|edit; pass position for move — omit it for create to ghost where the canvas will place the node, and the linked create lands there — nodeId for move/edit/remove, edge for connect; optional label, reason, confidence 0..1, seq, ttlMs, and a stable id); "update" patches a live intent by id; "clear" abandons/dissolves it. To make veto authoritative, pass the returned id as intentId on the real canvas_node/canvas_edge/canvas_group/canvas_render mutation: vetoed or expired intents are rejected, and a successful linked mutation settles the ghost automatically. Intents are ephemeral presence: never persisted, never snapshotted, auto-expire (~8s).',
|
|
350
351
|
actionSummary: 'signal | update | clear',
|
|
351
352
|
actions: {
|
|
352
353
|
signal: 'intent.signal',
|