pmx-canvas 0.8.2 → 0.9.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/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +601 -22
- package/CHANGELOG.md +59 -0
- package/Readme.md +131 -532
- package/dist/canvas/global.css +2724 -476
- package/dist/canvas/index.js +168 -107
- package/dist/canvas/mcp-app.html +2899 -596
- 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/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/human-store.d.ts +2 -1
- 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 +52 -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 +449 -16
- package/docs/environment.md +0 -1
- package/docs/evals/curation-effect.md +128 -0
- package/docs/http-api.md +459 -37
- package/docs/mcp.md +146 -18
- 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 +216 -42
- package/skills/pmx-canvas/evals/evals.json +224 -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 +88 -11
- package/src/cli/commands/edges.ts +28 -0
- package/src/cli/commands/groups.ts +7 -0
- package/src/cli/commands/nodes.ts +1 -0
- package/src/cli/commands/query.ts +34 -2
- package/src/cli/commands/record.ts +199 -48
- package/src/cli/index.ts +6 -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 +543 -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 +6 -3
- package/src/client/nodes/FileNode.tsx +46 -42
- package/src/client/nodes/HtmlNode.tsx +32 -0
- 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/human-store.ts +13 -7
- 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 +235 -32
- package/src/client/state/workspace-settings.ts +11 -0
- package/src/client/theme/global.css +2724 -476
- 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.tsx +31 -0
- 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 +195 -83
- 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 +178 -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 +181 -11
- 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 +26 -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/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 +74 -25
- 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;
|
|
@@ -25,6 +27,7 @@ export const CONTEXT_READ_OPS = new Set([
|
|
|
25
27
|
'spatial.get',
|
|
26
28
|
'layout.get',
|
|
27
29
|
'context.get',
|
|
30
|
+
'memory-graph.get',
|
|
28
31
|
]);
|
|
29
32
|
|
|
30
33
|
export interface ContextRead {
|
|
@@ -40,16 +43,22 @@ export interface ContextRead {
|
|
|
40
43
|
deliveredNodeIds: string[];
|
|
41
44
|
/** Every node on the read board whose content was delivered, with its content revision at read time. */
|
|
42
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>;
|
|
43
48
|
bytes: number;
|
|
44
49
|
/** The board open when the read happened (null on Home). */
|
|
45
50
|
boardId: string | null;
|
|
46
51
|
}
|
|
47
52
|
|
|
48
|
-
export type ContextReadInput = Omit<ContextRead, 'seq' | 'id' | 'at' | 'boardId' | 'readNodes'> & {
|
|
53
|
+
export type ContextReadInput = Omit<ContextRead, 'seq' | 'id' | 'at' | 'boardId' | 'readNodes' | 'seenLinks'> & {
|
|
49
54
|
/** Explicit read target, captured before asynchronous formatting/proxy work. */
|
|
50
55
|
boardId?: string | null;
|
|
51
56
|
/** Ids of every serialized node in what the reader received; the server keeps those on the board. */
|
|
52
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>;
|
|
53
62
|
};
|
|
54
63
|
|
|
55
64
|
/** One node's read state on a board: the latest agent read that delivered its content. */
|
|
@@ -78,10 +87,13 @@ export interface ContextReadConsumerSummary {
|
|
|
78
87
|
* `nodeId` with its text) in what the reader received. A bare id list, a
|
|
79
88
|
* title, or a clipped-off node does not count — the agent got the node's
|
|
80
89
|
* name, not its content. Non-node ids (edges, intents) are dropped by the
|
|
81
|
-
* 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.
|
|
82
93
|
*/
|
|
83
|
-
export function deliveredIds(payloadText: string): Set<string> {
|
|
84
|
-
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>();
|
|
85
97
|
const objectStarts: number[] = [];
|
|
86
98
|
let inString = false;
|
|
87
99
|
let escaped = false;
|
|
@@ -103,20 +115,21 @@ export function deliveredIds(payloadText: string): Set<string> {
|
|
|
103
115
|
if (start === undefined) continue;
|
|
104
116
|
try {
|
|
105
117
|
const value = JSON.parse(payloadText.slice(start, index + 1)) as unknown;
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
+
}
|
|
120
133
|
}
|
|
121
134
|
} catch {
|
|
122
135
|
// An inner object may be complete even when its containing object was clipped.
|
|
@@ -124,13 +137,27 @@ export function deliveredIds(payloadText: string): Set<string> {
|
|
|
124
137
|
}
|
|
125
138
|
}
|
|
126
139
|
|
|
127
|
-
return
|
|
140
|
+
return { read, reached };
|
|
128
141
|
}
|
|
129
142
|
|
|
130
|
-
/**
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
+
);
|
|
134
161
|
}
|
|
135
162
|
|
|
136
163
|
export function contextReadFromPayload(
|
|
@@ -138,15 +165,104 @@ export function contextReadFromPayload(
|
|
|
138
165
|
payload: unknown,
|
|
139
166
|
): ContextReadInput {
|
|
140
167
|
const text = typeof payload === 'string' ? payload : (JSON.stringify(payload) ?? '');
|
|
141
|
-
|
|
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);
|
|
142
189
|
return {
|
|
143
190
|
...base,
|
|
144
|
-
deliveredNodeIds: base.pinnedNodeIds.filter((id) =>
|
|
145
|
-
readNodeIds: [...
|
|
191
|
+
deliveredNodeIds: base.pinnedNodeIds.filter((id) => reached.has(id)),
|
|
192
|
+
readNodeIds: [...read],
|
|
193
|
+
seenNodeIds: Object.keys(seenLinkRevisions),
|
|
194
|
+
seenLinkRevisions,
|
|
146
195
|
bytes: Buffer.byteLength(text, 'utf-8'),
|
|
147
196
|
};
|
|
148
197
|
}
|
|
149
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
|
+
|
|
150
266
|
export const CONTEXT_READS_SCHEMA_SQL = `
|
|
151
267
|
CREATE TABLE IF NOT EXISTS context_reads (
|
|
152
268
|
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -161,7 +277,8 @@ export const CONTEXT_READS_SCHEMA_SQL = `
|
|
|
161
277
|
delivered_node_ids TEXT NOT NULL DEFAULT '[]',
|
|
162
278
|
bytes INTEGER NOT NULL DEFAULT 0,
|
|
163
279
|
board_id TEXT,
|
|
164
|
-
read_nodes TEXT NOT NULL DEFAULT '{}'
|
|
280
|
+
read_nodes TEXT NOT NULL DEFAULT '{}',
|
|
281
|
+
seen_links TEXT NOT NULL DEFAULT '{}'
|
|
165
282
|
);
|
|
166
283
|
`;
|
|
167
284
|
|
|
@@ -170,11 +287,12 @@ export function appendContextReadToDB(
|
|
|
170
287
|
input: ContextReadInput,
|
|
171
288
|
boardId: string | null,
|
|
172
289
|
readNodes: Record<string, number> = {},
|
|
290
|
+
seenLinks: Record<string, number> = {},
|
|
173
291
|
): ContextRead {
|
|
174
292
|
const id = `read-${randomUUID()}`;
|
|
175
293
|
const at = new Date().toISOString();
|
|
176
294
|
db.run(
|
|
177
|
-
'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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
178
296
|
[
|
|
179
297
|
id,
|
|
180
298
|
at,
|
|
@@ -188,12 +306,13 @@ export function appendContextReadToDB(
|
|
|
188
306
|
input.bytes,
|
|
189
307
|
boardId,
|
|
190
308
|
JSON.stringify(readNodes),
|
|
309
|
+
JSON.stringify(seenLinks),
|
|
191
310
|
],
|
|
192
311
|
);
|
|
193
312
|
const seq = Number(db.query<{ seq: number }, []>('SELECT last_insert_rowid() AS seq').get()?.seq ?? 0);
|
|
194
313
|
db.run('DELETE FROM context_reads WHERE seq <= ?', [seq - CONTEXT_READ_RETENTION]);
|
|
195
|
-
const { readNodeIds: _readNodeIds, ...record } = input;
|
|
196
|
-
return { ...record, seq, id, at, boardId, readNodes };
|
|
314
|
+
const { readNodeIds: _readNodeIds, seenNodeIds: _seenNodeIds, ...record } = input;
|
|
315
|
+
return { ...record, seq, id, at, boardId, readNodes, seenLinks };
|
|
197
316
|
}
|
|
198
317
|
|
|
199
318
|
interface ContextReadRow {
|
|
@@ -210,6 +329,7 @@ interface ContextReadRow {
|
|
|
210
329
|
bytes: number;
|
|
211
330
|
board_id: string | null;
|
|
212
331
|
read_nodes: string;
|
|
332
|
+
seen_links: string;
|
|
213
333
|
}
|
|
214
334
|
|
|
215
335
|
function rowToContextRead(row: ContextReadRow): ContextRead {
|
|
@@ -225,6 +345,7 @@ function rowToContextRead(row: ContextReadRow): ContextRead {
|
|
|
225
345
|
pinnedNodeIds: JSON.parse(row.pinned_node_ids) as string[],
|
|
226
346
|
deliveredNodeIds: JSON.parse(row.delivered_node_ids) as string[],
|
|
227
347
|
readNodes: JSON.parse(row.read_nodes) as Record<string, number>,
|
|
348
|
+
seenLinks: JSON.parse(row.seen_links) as Record<string, number>,
|
|
228
349
|
bytes: row.bytes,
|
|
229
350
|
boardId: row.board_id,
|
|
230
351
|
};
|
|
@@ -255,6 +376,33 @@ export function loadNodeReadStatusFromDB(db: Database, boardId: string): NodeRea
|
|
|
255
376
|
return [...status.values()];
|
|
256
377
|
}
|
|
257
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
|
+
|
|
258
406
|
/** Every node on a board whose content an agent read at or after `since` (ISO). */
|
|
259
407
|
export function loadReadNodeIdsSince(db: Database, boardId: string, since: string): string[] {
|
|
260
408
|
const rows = db
|