pmx-canvas 0.8.0 → 0.9.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/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +601 -22
- package/CHANGELOG.md +66 -0
- package/Readme.md +131 -532
- package/dist/canvas/global.css +2759 -510
- package/dist/canvas/index.js +168 -107
- package/dist/canvas/mcp-app.html +2936 -632
- package/dist/json-render/index.css +81 -1
- package/dist/json-render/index.js +20 -20
- package/dist/types/client/canvas/BarHint.d.ts +12 -2
- package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -1
- package/dist/types/client/canvas/BoardPinButton.d.ts +10 -0
- package/dist/types/client/canvas/BoardSwitcher.d.ts +1 -1
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandBar.d.ts +3 -4
- package/dist/types/client/canvas/ConnectionBanner.d.ts +2 -1
- package/dist/types/client/canvas/ContextChip.d.ts +3 -0
- package/dist/types/client/canvas/CrossBoardReferences.d.ts +5 -0
- package/dist/types/client/canvas/EdgeLayer.d.ts +14 -3
- package/dist/types/client/canvas/EmptyState.d.ts +4 -4
- package/dist/types/client/canvas/HomeGraphView.d.ts +2 -0
- package/dist/types/client/canvas/HomeMapView.d.ts +1 -0
- package/dist/types/client/canvas/HomeReviewView.d.ts +6 -0
- package/dist/types/client/canvas/HomeView.d.ts +9 -7
- package/dist/types/client/canvas/KnowledgeDialog.d.ts +4 -0
- package/dist/types/client/canvas/LifecycleChrome.d.ts +5 -0
- package/dist/types/client/canvas/LifecycleFields.d.ts +7 -0
- package/dist/types/client/canvas/LifecyclePanel.d.ts +1 -0
- package/dist/types/client/canvas/MemoryInspector.d.ts +7 -0
- package/dist/types/client/canvas/NearPinLayer.d.ts +14 -0
- package/dist/types/client/canvas/NodeContextMark.d.ts +40 -7
- package/dist/types/client/canvas/RelationPresetsDialog.d.ts +4 -0
- package/dist/types/client/canvas/RelationsPanel.d.ts +4 -0
- package/dist/types/client/canvas/SessionPanel.d.ts +2 -0
- package/dist/types/client/canvas/canvas-area.d.ts +1 -1
- package/dist/types/client/canvas/graph-geometry.d.ts +27 -0
- package/dist/types/client/canvas/graph-model.d.ts +37 -0
- package/dist/types/client/canvas/memory-layout.d.ts +23 -0
- package/dist/types/client/canvas/use-chrome-height.d.ts +2 -0
- package/dist/types/client/canvas/use-focus-trap.d.ts +1 -0
- package/dist/types/client/canvas/use-memory-graph.d.ts +8 -0
- package/dist/types/client/canvas/use-pan-zoom.d.ts +4 -1
- package/dist/types/client/icons.d.ts +0 -10
- package/dist/types/client/nodes/AxStepControls.d.ts +1 -1
- package/dist/types/client/nodes/HtmlNode.d.ts +1 -0
- package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +4 -4
- package/dist/types/client/nodes/MarkdownNode.d.ts +5 -1
- package/dist/types/client/nodes/McpAppNode.d.ts +4 -4
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -1
- package/dist/types/client/state/boards-store.d.ts +81 -9
- package/dist/types/client/state/canvas-store.d.ts +14 -1
- package/dist/types/client/state/context-chip-store.d.ts +22 -0
- package/dist/types/client/state/context-status-store.d.ts +35 -0
- package/dist/types/client/state/intent-bridge.d.ts +11 -8
- package/dist/types/client/state/knowledge-store.d.ts +22 -0
- package/dist/types/client/state/near-pin-store.d.ts +44 -1
- package/dist/types/client/state/presence-store.d.ts +4 -4
- package/dist/types/client/state/presentation.d.ts +28 -0
- package/dist/types/client/state/relation-store.d.ts +10 -0
- package/dist/types/client/state/session-store.d.ts +41 -2
- package/dist/types/client/state/workspace-settings.d.ts +7 -0
- package/dist/types/client/types.d.ts +13 -1
- package/dist/types/client/utils/keyboard.d.ts +2 -0
- package/dist/types/client/utils/time-ago.d.ts +4 -0
- package/dist/types/mcp/context-uri-template.d.ts +3 -1
- package/dist/types/server/agent-presence.d.ts +10 -3
- package/dist/types/server/ax-context.d.ts +1 -1
- package/dist/types/server/canvas-db.d.ts +30 -7
- package/dist/types/server/canvas-operations.d.ts +24 -2
- package/dist/types/server/canvas-provenance.d.ts +22 -2
- package/dist/types/server/canvas-serialization.d.ts +2 -0
- package/dist/types/server/canvas-state.d.ts +93 -15
- package/dist/types/server/canvas-validation.d.ts +1 -1
- package/dist/types/server/context-brief.d.ts +94 -6
- package/dist/types/server/context-reads.d.ts +33 -7
- package/dist/types/server/edit-summary.d.ts +20 -0
- package/dist/types/server/index.d.ts +71 -10
- package/dist/types/server/knowledge-lifecycle.d.ts +221 -0
- package/dist/types/server/lifecycle-status.d.ts +10 -0
- package/dist/types/server/memory-graph.d.ts +9 -0
- package/dist/types/server/mutation-history.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-shared.d.ts +1 -1
- package/dist/types/server/operations/ops/boards.d.ts +2 -1
- package/dist/types/server/operations/ops/lifecycle.d.ts +2 -0
- package/dist/types/server/operations/ops/memory-graph.d.ts +2 -0
- package/dist/types/server/operations/ops/nodes.d.ts +5 -0
- package/dist/types/server/operations/ops/query.d.ts +23 -0
- package/dist/types/server/operations/registry.d.ts +1 -0
- package/dist/types/server/placement.d.ts +7 -0
- package/dist/types/server/scope-fence.d.ts +2 -1
- package/dist/types/server/spatial-analysis.d.ts +6 -3
- package/dist/types/server/tour-control.d.ts +32 -0
- package/dist/types/server/wiki-links.d.ts +14 -0
- package/dist/types/server/workbench-auth.d.ts +2 -0
- package/dist/types/shared/agent-context.d.ts +2 -0
- package/dist/types/shared/agent-presence.d.ts +15 -12
- package/dist/types/shared/auto-arrange.d.ts +1 -0
- package/dist/types/shared/boards.d.ts +4 -4
- package/dist/types/shared/knowledge-lifecycle.d.ts +41 -0
- package/dist/types/shared/knowledge-presets.d.ts +6 -0
- package/dist/types/shared/lifecycle-status.d.ts +24 -0
- package/dist/types/shared/memory-graph.d.ts +53 -0
- package/dist/types/shared/relations.d.ts +68 -0
- package/dist/types/shared/semantic-attention.d.ts +2 -0
- package/dist/types/shared/tour.d.ts +38 -0
- package/dist/types/shared/workspace-settings.d.ts +11 -0
- package/docs/RELEASE.md +21 -3
- package/docs/adr-001-bun-only-runtime.md +7 -1
- package/docs/adr-002-shared-product-separate-runtimes.md +93 -0
- package/docs/api-reference.md +109 -0
- package/docs/ax-host-adapter-contract.md +1 -1
- package/docs/ax-state-contract.md +5 -4
- package/docs/cli.md +153 -15
- package/docs/demos/pmx-canvas-demo.mp4 +0 -0
- package/docs/design/AgentContext.dc.html +3 -3
- package/docs/design/BoardPins.dc.html +37 -22
- package/docs/design/Categories.dc.html +208 -0
- package/docs/design/CategoryEditor.dc.html +33 -0
- package/docs/design/Chrome.dc.html +55 -9
- package/docs/design/ContextChip.dc.html +224 -0
- package/docs/design/ContextChip600.dc.html +136 -0
- package/docs/design/EmptyState.dc.html +45 -0
- package/docs/design/Flows.dc.html +23 -1
- package/docs/design/HeaderMarks.dc.html +183 -0
- package/docs/design/Home.dc.html +19 -1
- package/docs/design/HomeGraph.dc.html +86 -202
- package/docs/design/HomeMap.dc.html +23 -1
- package/docs/design/HomeTeam.dc.html +32 -14
- package/docs/design/Lifecycle.dc.html +22 -0
- package/docs/design/Lifecycle600.dc.html +18 -0
- package/docs/design/LifecycleVariants.dc.html +157 -0
- package/docs/design/LinkAuthorship.dc.html +139 -0
- package/docs/design/LinksChanged.dc.html +203 -0
- package/docs/design/LinksOptions.dc.html +137 -0
- package/docs/design/LinksOptions600.dc.html +134 -0
- package/docs/design/NearPin.dc.html +9 -10
- package/docs/design/Pane600.dc.html +4 -3
- package/docs/design/Relations.dc.html +3 -2
- package/docs/design/Restore.dc.html +152 -0
- package/docs/design/SeeChange.dc.html +267 -0
- package/docs/design/Uses.dc.html +12 -6
- package/docs/design/canvas.json +146 -19
- package/docs/design.md +454 -18
- package/docs/environment.md +0 -1
- package/docs/evals/curation-effect.md +128 -0
- package/docs/http-api.md +465 -38
- package/docs/mcp.md +150 -19
- package/docs/node-types.md +58 -3
- package/docs/plans/design-gaps-2026-10-08.md +53 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +379 -0
- package/docs/plans/plan-016-context-made-visible-wave-1.md +150 -5
- package/docs/plans/plan-017-knowledge-categories-and-lifecycle.md +175 -0
- package/docs/plans/plan-018-connected-memory-wave-2.md +594 -0
- package/docs/plans/plan-019-post-0.9-roadmap.md +198 -0
- package/docs/product-context-vision-2026-09.md +17 -1
- package/docs/product-vision-2026-09.md +1082 -43
- package/docs/product-vision-review-2026-09.md +13 -1
- package/docs/screenshots/workbench-live-dark.png +0 -0
- package/docs/screenshots/workbench-live-light.png +0 -0
- package/docs/sdk.md +62 -2
- package/package.json +1 -1
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/plugin.json +1 -1
- package/plugins/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/tools.json +601 -22
- package/skills/pmx-canvas/SKILL.md +234 -43
- package/skills/pmx-canvas/evals/evals.json +62 -0
- package/skills/pmx-canvas/references/ax-html-control-surface.md +6 -0
- package/skills/pmx-canvas/references/full-reference.md +120 -21
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
- package/skills/pmx-canvas/references/importing-context.md +132 -0
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
- package/skills/pmx-canvas/references/tours-and-recording.md +16 -1
- package/skills/pmx-canvas-testing/SKILL.md +20 -0
- package/src/cli/agent.ts +2 -0
- package/src/cli/commands/ax.ts +12 -0
- package/src/cli/commands/boards.ts +87 -11
- package/src/cli/commands/edges.ts +28 -0
- package/src/cli/commands/groups.ts +7 -0
- package/src/cli/commands/nodes.ts +4 -2
- package/src/cli/commands/query.ts +34 -2
- package/src/cli/commands/record.ts +199 -48
- package/src/cli/index.ts +7 -1
- package/src/client/App.tsx +177 -70
- package/src/client/canvas/AttentionHistory.tsx +0 -2
- package/src/client/canvas/BarHint.tsx +40 -2
- package/src/client/canvas/BoardCopyDialog.tsx +1 -1
- package/src/client/canvas/BoardFolderDialog.tsx +7 -7
- package/src/client/canvas/BoardLinkDialog.tsx +138 -33
- package/src/client/canvas/BoardPinButton.tsx +33 -0
- package/src/client/canvas/BoardSwitcher.tsx +26 -40
- package/src/client/canvas/CanvasNode.tsx +360 -237
- package/src/client/canvas/CanvasViewport.tsx +45 -7
- package/src/client/canvas/CommandBar.tsx +29 -23
- package/src/client/canvas/CommandPalette.tsx +5 -12
- package/src/client/canvas/ConnectionBanner.tsx +3 -3
- package/src/client/canvas/ContextChip.tsx +296 -0
- package/src/client/canvas/ContextMenu.tsx +63 -5
- package/src/client/canvas/CrossBoardReferences.tsx +60 -0
- package/src/client/canvas/EdgeLayer.tsx +339 -76
- package/src/client/canvas/EmptyState.tsx +33 -18
- package/src/client/canvas/ExpandedNodeOverlay.tsx +72 -40
- package/src/client/canvas/ExternalWriters.tsx +57 -42
- package/src/client/canvas/HomeGraphView.tsx +942 -0
- package/src/client/canvas/HomeMapView.tsx +641 -0
- package/src/client/canvas/HomeReviewView.tsx +167 -0
- package/src/client/canvas/HomeView.tsx +687 -160
- package/src/client/canvas/IntentLayer.tsx +19 -12
- package/src/client/canvas/KnowledgeDialog.tsx +488 -0
- package/src/client/canvas/LifecycleChrome.tsx +133 -0
- package/src/client/canvas/LifecycleFields.tsx +91 -0
- package/src/client/canvas/LifecyclePanel.tsx +533 -0
- package/src/client/canvas/MemoryInspector.tsx +104 -0
- package/src/client/canvas/Minimap.tsx +3 -3
- package/src/client/canvas/NearPinLayer.tsx +187 -0
- package/src/client/canvas/NodeContextMark.tsx +175 -97
- package/src/client/canvas/Presentation.tsx +23 -57
- package/src/client/canvas/RelationPresetsDialog.tsx +413 -0
- package/src/client/canvas/RelationsPanel.tsx +82 -0
- package/src/client/canvas/SelectionBar.tsx +3 -6
- package/src/client/canvas/SessionPanel.tsx +90 -50
- package/src/client/canvas/SessionReceipt.tsx +311 -39
- package/src/client/canvas/ShortcutOverlay.tsx +1 -1
- package/src/client/canvas/SnapshotPanel.tsx +27 -36
- package/src/client/canvas/ToolRail.tsx +367 -192
- package/src/client/canvas/TopBar.tsx +354 -124
- package/src/client/canvas/canvas-area.ts +10 -3
- package/src/client/canvas/graph-geometry.ts +195 -0
- package/src/client/canvas/graph-model.ts +254 -0
- package/src/client/canvas/memory-layout.ts +106 -0
- package/src/client/canvas/use-chrome-height.ts +20 -0
- package/src/client/canvas/use-focus-trap.ts +31 -5
- package/src/client/canvas/use-memory-graph.ts +58 -0
- package/src/client/canvas/use-node-drag.ts +2 -1
- package/src/client/canvas/use-pan-zoom.ts +64 -7
- package/src/client/icons.tsx +0 -63
- package/src/client/mcp-app/style.css +0 -6
- package/src/client/nodes/AxStepControls.tsx +74 -50
- package/src/client/nodes/BoardNode.tsx +5 -1
- package/src/client/nodes/ContextNode.tsx +18 -11
- package/src/client/nodes/ExtAppFrame.tsx +0 -1
- package/src/client/nodes/FileNode.tsx +46 -42
- package/src/client/nodes/HtmlNode.tsx +44 -2
- package/src/client/nodes/ImageNode.tsx +1 -1
- package/src/client/nodes/InlineFormatBar.tsx +6 -9
- package/src/client/nodes/InlineMarkdownEditor.tsx +56 -17
- package/src/client/nodes/MarkdownNode.tsx +377 -109
- package/src/client/nodes/McpAppNode.tsx +50 -20
- package/src/client/nodes/MdFormatBar.tsx +21 -18
- package/src/client/nodes/PromptNode.tsx +1 -1
- package/src/client/nodes/ResponseNode.tsx +1 -1
- package/src/client/nodes/viewer-runtime-state.ts +54 -22
- package/src/client/state/attention-bridge.ts +64 -15
- package/src/client/state/boards-store.ts +193 -39
- package/src/client/state/canvas-store.ts +99 -12
- package/src/client/state/context-chip-store.ts +70 -0
- package/src/client/state/context-status-store.ts +53 -0
- package/src/client/state/intent-bridge.ts +51 -30
- package/src/client/state/knowledge-store.ts +43 -0
- package/src/client/state/near-pin-store.ts +115 -6
- package/src/client/state/presence-store.ts +7 -10
- package/src/client/state/presentation.ts +91 -0
- package/src/client/state/relation-store.ts +81 -0
- package/src/client/state/session-store.ts +144 -18
- package/src/client/state/sse-bridge.ts +206 -29
- package/src/client/state/workspace-settings.ts +11 -0
- package/src/client/theme/global.css +2759 -510
- package/src/client/types.ts +12 -0
- package/src/client/utils/keyboard.ts +10 -0
- package/src/client/utils/time-ago.ts +14 -0
- package/src/json-render/renderer/index.css +6 -1
- package/src/json-render/renderer/index.tsx +31 -1
- package/src/mcp/canvas-app.ts +12 -2
- package/src/mcp/context-uri-template.ts +7 -4
- package/src/mcp/server.ts +63 -41
- package/src/mcp/workbench-app.ts +39 -6
- package/src/server/agent-presence.ts +128 -28
- package/src/server/ax-context.ts +27 -13
- package/src/server/board-export.ts +12 -1
- package/src/server/canvas-db.ts +331 -42
- package/src/server/canvas-operations.ts +196 -84
- package/src/server/canvas-provenance.ts +53 -22
- package/src/server/canvas-serialization.ts +2 -0
- package/src/server/canvas-state.ts +950 -122
- package/src/server/canvas-validation.ts +9 -2
- package/src/server/context-brief.ts +345 -38
- package/src/server/context-reads.ts +179 -30
- package/src/server/demo-state.json +355 -24
- package/src/server/edit-summary.ts +74 -0
- package/src/server/html-surface.ts +8 -1
- package/src/server/index.ts +203 -13
- package/src/server/knowledge-lifecycle.ts +304 -0
- package/src/server/lifecycle-status.ts +232 -0
- package/src/server/memory-graph.ts +166 -0
- package/src/server/mutation-history.ts +18 -6
- package/src/server/operations/composites.ts +32 -11
- package/src/server/operations/http.ts +7 -2
- package/src/server/operations/index.ts +4 -0
- package/src/server/operations/mcp.ts +32 -1
- package/src/server/operations/ops/ax-presence.ts +1 -1
- package/src/server/operations/ops/ax-read.ts +57 -6
- package/src/server/operations/ops/ax-timeline.ts +28 -2
- package/src/server/operations/ops/backup.ts +5 -3
- package/src/server/operations/ops/batch.ts +35 -4
- package/src/server/operations/ops/boards.ts +188 -47
- package/src/server/operations/ops/canvas-wire.ts +31 -1
- package/src/server/operations/ops/edges.ts +36 -2
- package/src/server/operations/ops/groups.ts +6 -1
- package/src/server/operations/ops/json-render.ts +5 -1
- package/src/server/operations/ops/lifecycle.ts +262 -0
- package/src/server/operations/ops/memory-graph.ts +45 -0
- package/src/server/operations/ops/nodes.ts +107 -24
- package/src/server/operations/ops/query.ts +281 -68
- package/src/server/operations/ops/snapshots.ts +73 -1
- package/src/server/operations/ops/validate.ts +5 -1
- package/src/server/operations/ops/viewport.ts +97 -3
- package/src/server/operations/registry.ts +77 -26
- package/src/server/placement.ts +29 -0
- package/src/server/scope-fence.ts +78 -6
- package/src/server/server.ts +115 -16
- package/src/server/spatial-analysis.ts +48 -23
- package/src/server/tour-control.ts +82 -0
- package/src/server/wiki-links.ts +137 -0
- package/src/server/workbench-auth.ts +11 -0
- package/src/shared/agent-context.ts +3 -0
- package/src/shared/agent-presence.ts +15 -15
- package/src/shared/auto-arrange.ts +2 -0
- package/src/shared/boards.ts +4 -4
- package/src/shared/knowledge-lifecycle.ts +22 -0
- package/src/shared/knowledge-presets.ts +80 -0
- package/src/shared/lifecycle-status.ts +26 -0
- package/src/shared/memory-graph.ts +74 -0
- package/src/shared/relations.ts +98 -0
- package/src/shared/semantic-attention.ts +15 -1
- package/src/shared/tour.ts +23 -5
- package/src/shared/workspace-settings.ts +13 -0
- package/dist/types/client/canvas/ContextPinBar.d.ts +0 -1
- package/dist/types/client/canvas/FocusFieldLayer.d.ts +0 -1
- package/dist/types/client/canvas/kind-colors.d.ts +0 -3
- package/dist/types/server/agent-context.d.ts +0 -22
- package/src/client/canvas/ContextPinBar.tsx +0 -25
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { Database } from 'bun:sqlite';
|
|
9
9
|
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import type { MemoryGraph } from '../shared/memory-graph.js';
|
|
11
|
+
import type { CompiledContextEntry } from './context-brief.js';
|
|
10
12
|
|
|
11
13
|
export const CONTEXT_READ_RETENTION = 5000;
|
|
12
14
|
const CONTEXT_READ_DEFAULT_LIMIT = 50;
|
|
@@ -17,6 +19,7 @@ export type ContextReadChannel = (typeof CONTEXT_READ_CHANNELS)[number];
|
|
|
17
19
|
|
|
18
20
|
/** Registry reads that hand an agent canvas context; recorded in executeOperation. */
|
|
19
21
|
export const CONTEXT_READ_OPS = new Set([
|
|
22
|
+
'node.get',
|
|
20
23
|
'pinned-context.get',
|
|
21
24
|
'ax.context.get',
|
|
22
25
|
'ax.get',
|
|
@@ -24,6 +27,7 @@ export const CONTEXT_READ_OPS = new Set([
|
|
|
24
27
|
'spatial.get',
|
|
25
28
|
'layout.get',
|
|
26
29
|
'context.get',
|
|
30
|
+
'memory-graph.get',
|
|
27
31
|
]);
|
|
28
32
|
|
|
29
33
|
export interface ContextRead {
|
|
@@ -39,16 +43,22 @@ export interface ContextRead {
|
|
|
39
43
|
deliveredNodeIds: string[];
|
|
40
44
|
/** Every node on the read board whose content was delivered, with its content revision at read time. */
|
|
41
45
|
readNodes: Record<string, number>;
|
|
46
|
+
/** Every node on the read board that arrived with its relations (summaries count), with its links revision then. */
|
|
47
|
+
seenLinks: Record<string, number>;
|
|
42
48
|
bytes: number;
|
|
43
49
|
/** The board open when the read happened (null on Home). */
|
|
44
50
|
boardId: string | null;
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
export type ContextReadInput = Omit<ContextRead, 'seq' | 'id' | 'at' | 'boardId' | 'readNodes'> & {
|
|
53
|
+
export type ContextReadInput = Omit<ContextRead, 'seq' | 'id' | 'at' | 'boardId' | 'readNodes' | 'seenLinks'> & {
|
|
48
54
|
/** Explicit read target, captured before asynchronous formatting/proxy work. */
|
|
49
55
|
boardId?: string | null;
|
|
50
56
|
/** Ids of every serialized node in what the reader received; the server keeps those on the board. */
|
|
51
57
|
readNodeIds?: string[];
|
|
58
|
+
/** Ids of every node that arrived, summaries included; defaults to readNodeIds. */
|
|
59
|
+
seenNodeIds?: string[];
|
|
60
|
+
/** Exact link clocks captured in the delivered projection, before asynchronous transport work. */
|
|
61
|
+
seenLinkRevisions?: Record<string, number>;
|
|
52
62
|
};
|
|
53
63
|
|
|
54
64
|
/** One node's read state on a board: the latest agent read that delivered its content. */
|
|
@@ -77,10 +87,13 @@ export interface ContextReadConsumerSummary {
|
|
|
77
87
|
* `nodeId` with its text) in what the reader received. A bare id list, a
|
|
78
88
|
* title, or a clipped-off node does not count — the agent got the node's
|
|
79
89
|
* name, not its content. Non-node ids (edges, intents) are dropped by the
|
|
80
|
-
* caller against the board's nodes.
|
|
90
|
+
* caller against the board's nodes. A summary is "seen", not "read": `read`
|
|
91
|
+
* holds only nodes pulled in full and `reached` adds the summaries. Content
|
|
92
|
+
* delivery alone never establishes that the card's relations were delivered.
|
|
81
93
|
*/
|
|
82
|
-
export function deliveredIds(payloadText: string): Set<string> {
|
|
83
|
-
const
|
|
94
|
+
export function deliveredIds(payloadText: string): { read: Set<string>; reached: Set<string> } {
|
|
95
|
+
const read = new Set<string>();
|
|
96
|
+
const reached = new Set<string>();
|
|
84
97
|
const objectStarts: number[] = [];
|
|
85
98
|
let inString = false;
|
|
86
99
|
let escaped = false;
|
|
@@ -102,20 +115,21 @@ export function deliveredIds(payloadText: string): Set<string> {
|
|
|
102
115
|
if (start === undefined) continue;
|
|
103
116
|
try {
|
|
104
117
|
const value = JSON.parse(payloadText.slice(start, index + 1)) as unknown;
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
119
|
+
const item = value as {
|
|
120
|
+
id?: unknown;
|
|
121
|
+
nodeId?: unknown;
|
|
122
|
+
titleOnly?: unknown;
|
|
123
|
+
summaryOnly?: unknown;
|
|
124
|
+
text?: unknown;
|
|
125
|
+
};
|
|
126
|
+
if (typeof item.id === 'string') {
|
|
127
|
+
read.add(item.id);
|
|
128
|
+
reached.add(item.id);
|
|
129
|
+
} else if (typeof item.nodeId === 'string' && item.titleOnly !== true && typeof item.text === 'string') {
|
|
130
|
+
reached.add(item.nodeId);
|
|
131
|
+
if (item.summaryOnly !== true) read.add(item.nodeId);
|
|
132
|
+
}
|
|
119
133
|
}
|
|
120
134
|
} catch {
|
|
121
135
|
// An inner object may be complete even when its containing object was clipped.
|
|
@@ -123,13 +137,27 @@ export function deliveredIds(payloadText: string): Set<string> {
|
|
|
123
137
|
}
|
|
124
138
|
}
|
|
125
139
|
|
|
126
|
-
return
|
|
140
|
+
return { read, reached };
|
|
127
141
|
}
|
|
128
142
|
|
|
129
|
-
/**
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
143
|
+
/** Only complete, board-qualified brief relations can acknowledge their captured clock. */
|
|
144
|
+
function briefLinkRevisions(entries: Partial<CompiledContextEntry>[], boardId: string): Record<string, number> {
|
|
145
|
+
return Object.fromEntries(
|
|
146
|
+
entries.flatMap((entry) =>
|
|
147
|
+
entry.sourceBoardId === boardId &&
|
|
148
|
+
typeof entry.nodeId === 'string' &&
|
|
149
|
+
!entry.nodeId.startsWith('board:') &&
|
|
150
|
+
typeof entry.text === 'string' &&
|
|
151
|
+
entry.titleOnly !== true &&
|
|
152
|
+
entry.truncated !== true &&
|
|
153
|
+
entry.linksComplete !== false &&
|
|
154
|
+
typeof entry.linksRevision === 'number' &&
|
|
155
|
+
Number.isSafeInteger(entry.linksRevision) &&
|
|
156
|
+
entry.linksRevision >= 0
|
|
157
|
+
? [[entry.nodeId, entry.linksRevision]]
|
|
158
|
+
: [],
|
|
159
|
+
),
|
|
160
|
+
);
|
|
133
161
|
}
|
|
134
162
|
|
|
135
163
|
export function contextReadFromPayload(
|
|
@@ -137,15 +165,104 @@ export function contextReadFromPayload(
|
|
|
137
165
|
payload: unknown,
|
|
138
166
|
): ContextReadInput {
|
|
139
167
|
const text = typeof payload === 'string' ? payload : (JSON.stringify(payload) ?? '');
|
|
140
|
-
|
|
168
|
+
let delivered = text;
|
|
169
|
+
let seenLinkRevisions: Record<string, number> = {};
|
|
170
|
+
try {
|
|
171
|
+
const document = JSON.parse(text) as {
|
|
172
|
+
board?: { boardId?: string };
|
|
173
|
+
entries?: Partial<CompiledContextEntry>[];
|
|
174
|
+
};
|
|
175
|
+
if (document.board?.boardId && Array.isArray(document.entries)) {
|
|
176
|
+
// A brief carries several boards. Bare IDs from a remote README must
|
|
177
|
+
// never acknowledge a same-ID card on the open board.
|
|
178
|
+
const entries = document.entries.filter(
|
|
179
|
+
(entry) => entry.sourceBoardId === (base.boardId ?? document.board?.boardId),
|
|
180
|
+
);
|
|
181
|
+
delivered = JSON.stringify(entries);
|
|
182
|
+
seenLinkRevisions = briefLinkRevisions(entries, base.boardId ?? document.board.boardId);
|
|
183
|
+
}
|
|
184
|
+
} catch {
|
|
185
|
+
// Non-JSON and clipped payloads keep the complete-object delivery scanner.
|
|
186
|
+
}
|
|
187
|
+
// Delivery (did the pins reach the agent?) counts summaries; read marks do not.
|
|
188
|
+
const { read, reached } = deliveredIds(delivered);
|
|
141
189
|
return {
|
|
142
190
|
...base,
|
|
143
|
-
deliveredNodeIds: base.pinnedNodeIds.filter((id) =>
|
|
144
|
-
readNodeIds: [...
|
|
191
|
+
deliveredNodeIds: base.pinnedNodeIds.filter((id) => reached.has(id)),
|
|
192
|
+
readNodeIds: [...read],
|
|
193
|
+
seenNodeIds: Object.keys(seenLinkRevisions),
|
|
194
|
+
seenLinkRevisions,
|
|
145
195
|
bytes: Buffer.byteLength(text, 'utf-8'),
|
|
146
196
|
};
|
|
147
197
|
}
|
|
148
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Reads a `context.get` brief made on pinned boards (vision move 0a): one per
|
|
201
|
+
* pinned board whose map arrived, so the board shows as read. Its cards came as
|
|
202
|
+
* summaries, which mark nothing read until the agent pulls them; only summaries
|
|
203
|
+
* with complete, unclipped relations count as links seen on that board.
|
|
204
|
+
*/
|
|
205
|
+
export function pinnedBoardReads(
|
|
206
|
+
base: Omit<ContextReadInput, 'deliveredNodeIds' | 'bytes' | 'readNodeIds' | 'pinnedNodeIds' | 'boardId'>,
|
|
207
|
+
payload: unknown,
|
|
208
|
+
): ContextReadInput[] {
|
|
209
|
+
let document: unknown = payload;
|
|
210
|
+
if (typeof payload === 'string') {
|
|
211
|
+
try {
|
|
212
|
+
document = JSON.parse(payload);
|
|
213
|
+
} catch {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const entries = (document as { entries?: Partial<CompiledContextEntry>[] } | null)?.entries ?? [];
|
|
218
|
+
return entries
|
|
219
|
+
.filter(
|
|
220
|
+
(entry) =>
|
|
221
|
+
entry.reason === 'pinned-board' &&
|
|
222
|
+
typeof entry.sourceBoardId === 'string' &&
|
|
223
|
+
entry.nodeId === `board:${entry.sourceBoardId}`,
|
|
224
|
+
)
|
|
225
|
+
.map((entry) => {
|
|
226
|
+
const boardId = entry.sourceBoardId as string;
|
|
227
|
+
const seenLinkRevisions = briefLinkRevisions(entries, boardId);
|
|
228
|
+
return {
|
|
229
|
+
...base,
|
|
230
|
+
boardId,
|
|
231
|
+
pinnedNodeIds: [],
|
|
232
|
+
deliveredNodeIds: [],
|
|
233
|
+
readNodeIds: [],
|
|
234
|
+
seenNodeIds: Object.keys(seenLinkRevisions),
|
|
235
|
+
seenLinkRevisions,
|
|
236
|
+
bytes: Buffer.byteLength(JSON.stringify(entry), 'utf-8'),
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Graph summaries are seen on their own boards, never full-content reads or a board switch. */
|
|
242
|
+
export function graphReads(
|
|
243
|
+
base: Pick<ContextReadInput, 'channel' | 'resource' | 'source' | 'consumer' | 'agentId'>,
|
|
244
|
+
graph: MemoryGraph,
|
|
245
|
+
): ContextReadInput[] {
|
|
246
|
+
const boards = new Set(graph.nodes.map((node) => node.boardId));
|
|
247
|
+
return [...boards].map((boardId) => {
|
|
248
|
+
const nodes = graph.nodes.filter((node) => node.boardId === boardId);
|
|
249
|
+
return {
|
|
250
|
+
...base,
|
|
251
|
+
boardId,
|
|
252
|
+
pinnedNodeIds: [],
|
|
253
|
+
deliveredNodeIds: [],
|
|
254
|
+
readNodeIds: [],
|
|
255
|
+
seenNodeIds: nodes.flatMap((node) => (node.cardId && node.linksComplete !== false ? [node.cardId] : [])),
|
|
256
|
+
seenLinkRevisions: Object.fromEntries(
|
|
257
|
+
nodes.flatMap((node) =>
|
|
258
|
+
node.cardId && typeof node.linksRevision === 'number' ? [[node.cardId, node.linksRevision]] : [],
|
|
259
|
+
),
|
|
260
|
+
),
|
|
261
|
+
bytes: Buffer.byteLength(JSON.stringify(nodes), 'utf-8'),
|
|
262
|
+
};
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
149
266
|
export const CONTEXT_READS_SCHEMA_SQL = `
|
|
150
267
|
CREATE TABLE IF NOT EXISTS context_reads (
|
|
151
268
|
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -160,7 +277,8 @@ export const CONTEXT_READS_SCHEMA_SQL = `
|
|
|
160
277
|
delivered_node_ids TEXT NOT NULL DEFAULT '[]',
|
|
161
278
|
bytes INTEGER NOT NULL DEFAULT 0,
|
|
162
279
|
board_id TEXT,
|
|
163
|
-
read_nodes TEXT NOT NULL DEFAULT '{}'
|
|
280
|
+
read_nodes TEXT NOT NULL DEFAULT '{}',
|
|
281
|
+
seen_links TEXT NOT NULL DEFAULT '{}'
|
|
164
282
|
);
|
|
165
283
|
`;
|
|
166
284
|
|
|
@@ -169,11 +287,12 @@ export function appendContextReadToDB(
|
|
|
169
287
|
input: ContextReadInput,
|
|
170
288
|
boardId: string | null,
|
|
171
289
|
readNodes: Record<string, number> = {},
|
|
290
|
+
seenLinks: Record<string, number> = {},
|
|
172
291
|
): ContextRead {
|
|
173
292
|
const id = `read-${randomUUID()}`;
|
|
174
293
|
const at = new Date().toISOString();
|
|
175
294
|
db.run(
|
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
295
|
+
'INSERT INTO context_reads (id, at, channel, resource, source, consumer, agent_id, pinned_node_ids, delivered_node_ids, bytes, board_id, read_nodes, seen_links) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
177
296
|
[
|
|
178
297
|
id,
|
|
179
298
|
at,
|
|
@@ -187,12 +306,13 @@ export function appendContextReadToDB(
|
|
|
187
306
|
input.bytes,
|
|
188
307
|
boardId,
|
|
189
308
|
JSON.stringify(readNodes),
|
|
309
|
+
JSON.stringify(seenLinks),
|
|
190
310
|
],
|
|
191
311
|
);
|
|
192
312
|
const seq = Number(db.query<{ seq: number }, []>('SELECT last_insert_rowid() AS seq').get()?.seq ?? 0);
|
|
193
313
|
db.run('DELETE FROM context_reads WHERE seq <= ?', [seq - CONTEXT_READ_RETENTION]);
|
|
194
|
-
const { readNodeIds: _readNodeIds, ...record } = input;
|
|
195
|
-
return { ...record, seq, id, at, boardId, readNodes };
|
|
314
|
+
const { readNodeIds: _readNodeIds, seenNodeIds: _seenNodeIds, ...record } = input;
|
|
315
|
+
return { ...record, seq, id, at, boardId, readNodes, seenLinks };
|
|
196
316
|
}
|
|
197
317
|
|
|
198
318
|
interface ContextReadRow {
|
|
@@ -209,6 +329,7 @@ interface ContextReadRow {
|
|
|
209
329
|
bytes: number;
|
|
210
330
|
board_id: string | null;
|
|
211
331
|
read_nodes: string;
|
|
332
|
+
seen_links: string;
|
|
212
333
|
}
|
|
213
334
|
|
|
214
335
|
function rowToContextRead(row: ContextReadRow): ContextRead {
|
|
@@ -224,6 +345,7 @@ function rowToContextRead(row: ContextReadRow): ContextRead {
|
|
|
224
345
|
pinnedNodeIds: JSON.parse(row.pinned_node_ids) as string[],
|
|
225
346
|
deliveredNodeIds: JSON.parse(row.delivered_node_ids) as string[],
|
|
226
347
|
readNodes: JSON.parse(row.read_nodes) as Record<string, number>,
|
|
348
|
+
seenLinks: JSON.parse(row.seen_links) as Record<string, number>,
|
|
227
349
|
bytes: row.bytes,
|
|
228
350
|
boardId: row.board_id,
|
|
229
351
|
};
|
|
@@ -254,6 +376,33 @@ export function loadNodeReadStatusFromDB(db: Database, boardId: string): NodeRea
|
|
|
254
376
|
return [...status.values()];
|
|
255
377
|
}
|
|
256
378
|
|
|
379
|
+
/** The latest agent read on a board, of any kind (a brief, a pinned-board map, a pull), or null. */
|
|
380
|
+
export function loadBoardLastReadFromDB(
|
|
381
|
+
db: Database,
|
|
382
|
+
boardId: string,
|
|
383
|
+
): { lastReadAt: string; lastReadBy: string } | null {
|
|
384
|
+
const row = db
|
|
385
|
+
.query<ContextReadRow, [string]>('SELECT * FROM context_reads WHERE board_id = ? ORDER BY seq DESC LIMIT 1')
|
|
386
|
+
.get(boardId);
|
|
387
|
+
if (!row) return null;
|
|
388
|
+
const read = rowToContextRead(row);
|
|
389
|
+
return { lastReadAt: read.at, lastReadBy: read.consumer ?? read.agentId ?? read.source };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Per node on a board, the newest links revision any agent read has carried (connection marks). */
|
|
393
|
+
export function loadSeenLinksFromDB(db: Database, boardId: string): Record<string, number> {
|
|
394
|
+
const rows = db
|
|
395
|
+
.query<{ seen_links: string }, [string]>('SELECT seen_links FROM context_reads WHERE board_id = ?')
|
|
396
|
+
.all(boardId);
|
|
397
|
+
const seen: Record<string, number> = {};
|
|
398
|
+
for (const row of rows) {
|
|
399
|
+
for (const [nodeId, revision] of Object.entries(JSON.parse(row.seen_links) as Record<string, number>)) {
|
|
400
|
+
if (revision > (seen[nodeId] ?? -1)) seen[nodeId] = revision;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return seen;
|
|
404
|
+
}
|
|
405
|
+
|
|
257
406
|
/** Every node on a board whose content an agent read at or after `since` (ISO). */
|
|
258
407
|
export function loadReadNodeIdsSince(db: Database, boardId: string, since: string): string[] {
|
|
259
408
|
const rows = db
|