pmx-canvas 0.1.36 → 0.2.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.
Files changed (91) hide show
  1. package/CHANGELOG.md +447 -0
  2. package/Readme.md +2 -2
  3. package/dist/json-render/index.js +89 -334
  4. package/dist/types/mcp/canvas-access.d.ts +5 -171
  5. package/dist/types/server/ax-state-manager.d.ts +267 -0
  6. package/dist/types/server/ax-state.d.ts +3 -1
  7. package/dist/types/server/canvas-db.d.ts +13 -0
  8. package/dist/types/server/canvas-operations.d.ts +1 -12
  9. package/dist/types/server/canvas-state.d.ts +8 -23
  10. package/dist/types/server/index.d.ts +6 -24
  11. package/dist/types/server/operations/composites.d.ts +121 -0
  12. package/dist/types/server/operations/http.d.ts +7 -0
  13. package/dist/types/server/operations/index.d.ts +8 -0
  14. package/dist/types/server/operations/invoker.d.ts +13 -0
  15. package/dist/types/server/operations/mcp.d.ts +15 -0
  16. package/dist/types/server/operations/ops/annotation.d.ts +2 -0
  17. package/dist/types/server/operations/ops/app.d.ts +33 -0
  18. package/dist/types/server/operations/ops/ax-await.d.ts +2 -0
  19. package/dist/types/server/operations/ops/ax-shared.d.ts +31 -0
  20. package/dist/types/server/operations/ops/ax-state.d.ts +2 -0
  21. package/dist/types/server/operations/ops/ax-timeline.d.ts +2 -0
  22. package/dist/types/server/operations/ops/ax-work.d.ts +2 -0
  23. package/dist/types/server/operations/ops/batch.d.ts +19 -0
  24. package/dist/types/server/operations/ops/edges.d.ts +2 -0
  25. package/dist/types/server/operations/ops/groups.d.ts +2 -0
  26. package/dist/types/server/operations/ops/json-render.d.ts +31 -0
  27. package/dist/types/server/operations/ops/nodes.d.ts +62 -0
  28. package/dist/types/server/operations/ops/query.d.ts +2 -0
  29. package/dist/types/server/operations/ops/snapshots.d.ts +2 -0
  30. package/dist/types/server/operations/ops/validate.d.ts +2 -0
  31. package/dist/types/server/operations/ops/viewport.d.ts +2 -0
  32. package/dist/types/server/operations/ops/webview.d.ts +2 -0
  33. package/dist/types/server/operations/registry.d.ts +15 -0
  34. package/dist/types/server/operations/types.d.ts +116 -0
  35. package/dist/types/server/operations/webview-runner.d.ts +69 -0
  36. package/docs/RELEASE.md +5 -0
  37. package/docs/adr-001-bun-only-runtime.md +46 -0
  38. package/docs/api-stability.md +57 -0
  39. package/docs/ax-host-adapter-contract.md +19 -1
  40. package/docs/ax-state-contract.md +72 -0
  41. package/docs/http-api.md +4 -0
  42. package/docs/mcp.md +61 -12
  43. package/docs/plans/plan-005-operation-registry.md +84 -0
  44. package/docs/plans/plan-006-mcp-tool-consolidation.md +109 -0
  45. package/docs/plans/plan-007-ax-domain.md +99 -0
  46. package/docs/plans/plan-008-registry-finish.md +91 -0
  47. package/docs/tech-debt-assessment-2026-06.md +90 -0
  48. package/package.json +3 -3
  49. package/skills/pmx-canvas/SKILL.md +221 -193
  50. package/skills/pmx-canvas/evals/evals.json +3 -3
  51. package/skills/pmx-canvas/references/ax-html-control-surface.md +93 -0
  52. package/skills/pmx-canvas/references/codex-app-adapter.md +13 -14
  53. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +26 -11
  54. package/src/cli/agent.ts +52 -31
  55. package/src/mcp/canvas-access.ts +30 -830
  56. package/src/mcp/server.ts +162 -2014
  57. package/src/server/ax-context.ts +8 -1
  58. package/src/server/ax-state-manager.ts +826 -0
  59. package/src/server/ax-state.ts +10 -2
  60. package/src/server/canvas-db.ts +35 -0
  61. package/src/server/canvas-operations.ts +2 -328
  62. package/src/server/canvas-schema.ts +2 -2
  63. package/src/server/canvas-state.ts +103 -465
  64. package/src/server/index.ts +54 -190
  65. package/src/server/operations/composites.ts +355 -0
  66. package/src/server/operations/http.ts +103 -0
  67. package/src/server/operations/index.ts +65 -0
  68. package/src/server/operations/invoker.ts +87 -0
  69. package/src/server/operations/mcp.ts +221 -0
  70. package/src/server/operations/ops/annotation.ts +60 -0
  71. package/src/server/operations/ops/app.ts +447 -0
  72. package/src/server/operations/ops/ax-await.ts +216 -0
  73. package/src/server/operations/ops/ax-shared.ts +38 -0
  74. package/src/server/operations/ops/ax-state.ts +249 -0
  75. package/src/server/operations/ops/ax-timeline.ts +381 -0
  76. package/src/server/operations/ops/ax-work.ts +635 -0
  77. package/src/server/operations/ops/batch.ts +365 -0
  78. package/src/server/operations/ops/edges.ts +166 -0
  79. package/src/server/operations/ops/groups.ts +176 -0
  80. package/src/server/operations/ops/json-render.ts +691 -0
  81. package/src/server/operations/ops/nodes.ts +1047 -0
  82. package/src/server/operations/ops/query.ts +281 -0
  83. package/src/server/operations/ops/snapshots.ts +366 -0
  84. package/src/server/operations/ops/validate.ts +37 -0
  85. package/src/server/operations/ops/viewport.ts +219 -0
  86. package/src/server/operations/ops/webview.ts +339 -0
  87. package/src/server/operations/registry.ts +79 -0
  88. package/src/server/operations/types.ts +150 -0
  89. package/src/server/operations/webview-runner.ts +77 -0
  90. package/src/server/server.ts +158 -2255
  91. package/src/server/web-artifacts.ts +6 -2
@@ -29,17 +29,6 @@ import {
29
29
  isDbPopulated,
30
30
  checkpointCanvasDb,
31
31
  finalizeCanvasDbForClose,
32
- appendAxEventToDB,
33
- appendAxEvidenceToDB,
34
- appendAxSteeringToDB,
35
- markAxSteeringDeliveredInDB,
36
- loadAxEventsFromDB,
37
- loadAxEvidenceFromDB,
38
- loadAxSteeringFromDB,
39
- loadPendingAxSteeringFromDB,
40
- loadAxTimelineSummaryFromDB,
41
- upsertAxHostCapabilityToDB,
42
- loadAxHostCapabilityFromDB,
43
32
  type PersistedCanvasState,
44
33
  type CanvasTheme,
45
34
  type AxTimelineQuery,
@@ -55,22 +44,6 @@ import {
55
44
  } from './placement.js';
56
45
  import {
57
46
  createEmptyAxState,
58
- createEmptyAxHostCapability,
59
- normalizeAxState,
60
- normalizeAxHostCapability,
61
- createAxWorkItem,
62
- createAxApprovalGate,
63
- createAxReviewAnnotation,
64
- createAxEvent,
65
- createAxEvidence,
66
- createAxSteeringMessage,
67
- createAxElicitation,
68
- createAxModeRequest,
69
- isAxCommand,
70
- listAxCommands,
71
- AX_COMMAND_REGISTRY,
72
- normalizeAxPolicy,
73
- mapAxActivityKindToEventKind,
74
47
  type PmxAxActivityKind,
75
48
  type PmxAxElicitation,
76
49
  type PmxAxModeRequest,
@@ -97,6 +70,7 @@ import {
97
70
  type PmxAxHostCapability,
98
71
  type PmxAxTimelineSummary,
99
72
  } from './ax-state.js';
73
+ import { AxStateManager } from './ax-state-manager.js';
100
74
 
101
75
  function logCanvasStateWarning(action: string, error: unknown, details?: Record<string, unknown>): void {
102
76
  console.warn(`[canvas-state] ${action}`, { error, ...(details ?? {}) });
@@ -329,10 +303,21 @@ class CanvasStateManager {
329
303
  private _viewport: ViewportState = { x: 0, y: 0, scale: 1 };
330
304
  private _theme: CanvasTheme = 'dark';
331
305
  private _contextPinnedNodeIds = new Set<string>();
332
- private _axState: PmxAxState = createEmptyAxState();
333
- private _axHostCapability: PmxAxHostCapability | null = null;
334
306
  private _workspaceRoot = process.cwd();
335
307
 
308
+ // ── AX state (canvas-bound + timeline + host partitions) ──────────
309
+ // Extracted into a dedicated manager (plan-007 Slice A). CanvasStateManager
310
+ // holds it and delegates its public AX methods so the SDK/HTTP/MCP surface is
311
+ // byte-stable; the manager receives the host hooks it needs as injected deps.
312
+ private readonly ax = new AxStateManager({
313
+ getNodeIds: () => this.currentNodeIdSet(),
314
+ getDb: () => this._db,
315
+ scheduleSave: () => this.scheduleSave(),
316
+ notifyChange: (type) => this.notifyChange(type),
317
+ recordMutation: (info) => this.recordMutation(info),
318
+ suppressed: (fn) => this.suppressed(fn),
319
+ });
320
+
336
321
  // ── Change listeners (for MCP resource notifications) ──────
337
322
  private _changeListeners: ((type: CanvasChangeType) => void)[] = [];
338
323
 
@@ -396,14 +381,6 @@ class CanvasStateManager {
396
381
  return new Set(this.nodes.keys());
397
382
  }
398
383
 
399
- private normalizeAxForCurrentNodes(state: unknown): PmxAxState {
400
- return normalizeAxState(state, this.currentNodeIdSet());
401
- }
402
-
403
- private applyAxState(state: PmxAxState): void {
404
- this._axState = this.normalizeAxForCurrentNodes(state);
405
- }
406
-
407
384
  private applyResolvedGroupBounds(
408
385
  group: CanvasNodeState,
409
386
  groupId: string,
@@ -899,13 +876,7 @@ class CanvasStateManager {
899
876
  /** Load canvas state from SQLite (or legacy JSON fallback). Call once on server startup. */
900
877
  loadFromDisk(options: LoadFromDiskOptions = {}): boolean {
901
878
  // Host capability lives in its own table (not snapshotted / not in PmxAxState).
902
- if (this._db) {
903
- try {
904
- this._axHostCapability = loadAxHostCapabilityFromDB(this._db);
905
- } catch (error) {
906
- logCanvasStateWarning('load host capability failed', error, {});
907
- }
908
- }
879
+ this.ax.loadHostCapabilityFromDb();
909
880
  // Try SQLite first (only if DB has been populated)
910
881
  if (this._db && isDbPopulated(this._db)) {
911
882
  try {
@@ -1029,7 +1000,7 @@ class CanvasStateManager {
1029
1000
  this.edges.clear();
1030
1001
  this.annotations.clear();
1031
1002
  this._contextPinnedNodeIds.clear();
1032
- this._axState = createEmptyAxState();
1003
+ this.ax.resetCanvasBound();
1033
1004
 
1034
1005
  this._viewport = {
1035
1006
  x: state.viewport?.x ?? 0,
@@ -1060,7 +1031,7 @@ class CanvasStateManager {
1060
1031
  if (this.nodes.has(pinId)) this._contextPinnedNodeIds.add(pinId);
1061
1032
  }
1062
1033
  }
1063
- this._axState = this.normalizeAxForCurrentNodes(state.ax);
1034
+ this.ax.applyPersistedAx(state.ax);
1064
1035
  }
1065
1036
 
1066
1037
  private readResolvedSnapshot(idOrName: string): {
@@ -1456,11 +1427,41 @@ class CanvasStateManager {
1456
1427
  this.nodes.delete(id);
1457
1428
  this.removeEdgesForNode(id);
1458
1429
  this._contextPinnedNodeIds.delete(id);
1459
- this.applyAxState(this._axState);
1430
+ // Re-normalize canvas-bound AX against the surviving node set. This strips the
1431
+ // dangling node ref from work items / approval gates / elicitations / mode
1432
+ // requests (re-anchored) and drops node-anchored review annotations (removed).
1433
+ // Previously SILENT — now audited (plan-007 Slice A): if the deleted node
1434
+ // orphaned anything, record one `note` timeline event so the human and a
1435
+ // resuming agent can see the work that changed instead of it changing quietly.
1436
+ const orphaned = this.ax.revalidateAfterNodeRemoval(id);
1460
1437
  this.scheduleSave();
1461
1438
  this.notifyChange('nodes');
1462
1439
  this.notifyChange('pins');
1463
1440
  this.notifyChange('ax');
1441
+ // Only record the audit note on a real (user-initiated) deletion. Undo/redo
1442
+ // replay removeNode inside `suppressed()` (_suppressRecordingDepth > 0); the
1443
+ // original deletion already recorded the note, so replaying must NOT append a
1444
+ // duplicate (the timeline is append-only). `revalidateAfterNodeRemoval` above
1445
+ // still runs unconditionally — only the timeline note is gated.
1446
+ const affected = orphaned.reanchoredIds.length > 0 || orphaned.removedReviewIds.length > 0 || orphaned.reanchoredFocus;
1447
+ if (existing && this._suppressRecordingDepth === 0 && affected) {
1448
+ const title = (existing.data.title as string) ?? id;
1449
+ const focusNote = orphaned.reanchoredFocus ? ' (focus anchor cleared)' : '';
1450
+ this.recordAxEvent(
1451
+ {
1452
+ kind: 'note',
1453
+ summary: `Node "${title}" deleted — re-anchored ${orphaned.reanchoredIds.length} AX item(s), removed ${orphaned.removedReviewIds.length} node-anchored review annotation(s).${focusNote}`,
1454
+ data: {
1455
+ systemEvent: 'ax-node-orphan',
1456
+ removedNodeId: id,
1457
+ reanchoredIds: orphaned.reanchoredIds,
1458
+ removedReviewIds: orphaned.removedReviewIds,
1459
+ reanchoredFocus: orphaned.reanchoredFocus,
1460
+ },
1461
+ },
1462
+ { source: 'system' },
1463
+ );
1464
+ }
1464
1465
  if (cloned) {
1465
1466
  this.recordMutation({
1466
1467
  operationType: 'removeNode',
@@ -1469,7 +1470,7 @@ class CanvasStateManager {
1469
1470
  inverse: this.suppressed(() => {
1470
1471
  this.addNode(structuredClone(cloned));
1471
1472
  for (const edge of connectedEdges) this.addEdge(structuredClone(edge));
1472
- this.applyAxState(oldAxState);
1473
+ this.ax.applyPersistedAx(oldAxState);
1473
1474
  this.scheduleSave();
1474
1475
  this.notifyChange('ax');
1475
1476
  }),
@@ -1728,73 +1729,35 @@ class CanvasStateManager {
1728
1729
  return new Set(this._contextPinnedNodeIds);
1729
1730
  }
1730
1731
 
1732
+ // ── AX state delegation (canvas-bound + timeline + host) ──────────
1733
+ // All AX state lives in `this.ax` (AxStateManager); these are byte-stable
1734
+ // delegations so SDK/HTTP/MCP keep calling canvasState.<method>(...) unchanged.
1731
1735
  getAxState(): PmxAxState {
1732
- return structuredClone(this.normalizeAxForCurrentNodes(this._axState));
1736
+ return this.ax.getAxState();
1733
1737
  }
1734
1738
 
1735
1739
  getAxFocus(): PmxAxFocusState {
1736
- return this.getAxState().focus;
1740
+ return this.ax.getAxFocus();
1737
1741
  }
1738
1742
 
1739
1743
  setAxFocus(nodeIds: string[], options: { source?: PmxAxSource; recordHistory?: boolean } = {}): PmxAxFocusState {
1740
- const oldAxState = this.getAxState();
1741
- const nextAxState: PmxAxState = {
1742
- ...oldAxState,
1743
- focus: {
1744
- nodeIds,
1745
- primaryNodeId: nodeIds[0] ?? null,
1746
- updatedAt: new Date().toISOString(),
1747
- source: options.source ?? 'api',
1748
- },
1749
- };
1750
- this.applyAxState(nextAxState);
1751
- const appliedAxState = this.getAxState();
1752
- this.scheduleSave();
1753
- this.notifyChange('ax');
1754
- if (options.recordHistory === false) return appliedAxState.focus;
1755
- this.recordMutation({
1756
- operationType: 'setAxFocus',
1757
- description: `Set AX focus (${appliedAxState.focus.nodeIds.length} nodes)`,
1758
- forward: this.suppressed(() => {
1759
- this.applyAxState(appliedAxState);
1760
- this.scheduleSave();
1761
- this.notifyChange('ax');
1762
- }),
1763
- inverse: this.suppressed(() => {
1764
- this.applyAxState(oldAxState);
1765
- this.scheduleSave();
1766
- this.notifyChange('ax');
1767
- }),
1768
- });
1769
- return appliedAxState.focus;
1744
+ return this.ax.setAxFocus(nodeIds, options);
1770
1745
  }
1771
1746
 
1772
1747
  clearAxFocus(): PmxAxFocusState {
1773
- return this.setAxFocus([], { source: 'system' });
1748
+ return this.ax.clearAxFocus();
1774
1749
  }
1775
1750
 
1776
1751
  // ── Work items (canvas-bound; snapshotted via getAxState blob) ────
1777
1752
  getWorkItems(): PmxAxWorkItem[] {
1778
- return this.getAxState().workItems;
1753
+ return this.ax.getWorkItems();
1779
1754
  }
1780
1755
 
1781
1756
  addWorkItem(
1782
1757
  input: { title: string; status?: PmxAxWorkItemStatus; detail?: string | null; nodeIds?: string[] },
1783
1758
  options: { source?: PmxAxSource } = {},
1784
1759
  ): PmxAxWorkItem {
1785
- const oldAxState = this.getAxState();
1786
- const item = createAxWorkItem(input, options.source ?? 'api', this.currentNodeIdSet());
1787
- this.applyAxState({ ...oldAxState, workItems: [...oldAxState.workItems, item] });
1788
- const applied = this.getAxState();
1789
- this.scheduleSave();
1790
- this.notifyChange('ax');
1791
- this.recordMutation({
1792
- operationType: 'addWorkItem',
1793
- description: `Added work item "${item.title}"`,
1794
- forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
1795
- inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
1796
- });
1797
- return applied.workItems.find((w) => w.id === item.id) ?? item;
1760
+ return this.ax.addWorkItem(input, options);
1798
1761
  }
1799
1762
 
1800
1763
  updateWorkItem(
@@ -1802,53 +1765,19 @@ class CanvasStateManager {
1802
1765
  patch: { title?: string; status?: PmxAxWorkItemStatus; detail?: string | null; nodeIds?: string[] },
1803
1766
  options: { source?: PmxAxSource } = {},
1804
1767
  ): PmxAxWorkItem | null {
1805
- const oldAxState = this.getAxState();
1806
- const existing = oldAxState.workItems.find((w) => w.id === id);
1807
- if (!existing) return null;
1808
- const merged: PmxAxWorkItem = {
1809
- ...existing,
1810
- ...(patch.title !== undefined ? { title: patch.title } : {}),
1811
- ...(patch.status !== undefined ? { status: patch.status } : {}),
1812
- ...(patch.detail !== undefined ? { detail: patch.detail } : {}),
1813
- ...(patch.nodeIds !== undefined ? { nodeIds: patch.nodeIds.filter((n) => this.nodes.has(n)) } : {}),
1814
- updatedAt: new Date().toISOString(),
1815
- source: options.source ?? existing.source,
1816
- };
1817
- this.applyAxState({ ...oldAxState, workItems: replaceById(oldAxState.workItems, merged) });
1818
- const applied = this.getAxState();
1819
- this.scheduleSave();
1820
- this.notifyChange('ax');
1821
- this.recordMutation({
1822
- operationType: 'updateWorkItem',
1823
- description: `Updated work item ${id}`,
1824
- forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
1825
- inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
1826
- });
1827
- return applied.workItems.find((w) => w.id === id) ?? null;
1768
+ return this.ax.updateWorkItem(id, patch, options);
1828
1769
  }
1829
1770
 
1830
1771
  // ── Approval gates (canvas-bound) ─────────────────────────────────
1831
1772
  getApprovalGates(): PmxAxApprovalGate[] {
1832
- return this.getAxState().approvalGates;
1773
+ return this.ax.getApprovalGates();
1833
1774
  }
1834
1775
 
1835
1776
  requestApproval(
1836
1777
  input: { title: string; detail?: string | null; action?: string | null; nodeIds?: string[] },
1837
1778
  options: { source?: PmxAxSource } = {},
1838
1779
  ): PmxAxApprovalGate {
1839
- const oldAxState = this.getAxState();
1840
- const gate = createAxApprovalGate(input, options.source ?? 'api', this.currentNodeIdSet());
1841
- this.applyAxState({ ...oldAxState, approvalGates: [...oldAxState.approvalGates, gate] });
1842
- const applied = this.getAxState();
1843
- this.scheduleSave();
1844
- this.notifyChange('ax');
1845
- this.recordMutation({
1846
- operationType: 'requestApproval',
1847
- description: `Requested approval "${gate.title}"`,
1848
- forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
1849
- inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
1850
- });
1851
- return applied.approvalGates.find((g) => g.id === gate.id) ?? gate;
1780
+ return this.ax.requestApproval(input, options);
1852
1781
  }
1853
1782
 
1854
1783
  resolveApproval(
@@ -1856,32 +1785,12 @@ class CanvasStateManager {
1856
1785
  decision: 'approved' | 'rejected',
1857
1786
  options: { resolution?: string; source?: PmxAxSource } = {},
1858
1787
  ): PmxAxApprovalGate | null {
1859
- const oldAxState = this.getAxState();
1860
- const gate = oldAxState.approvalGates.find((g) => g.id === id);
1861
- if (!gate || gate.status !== 'pending') return null;
1862
- const resolved: PmxAxApprovalGate = {
1863
- ...gate,
1864
- status: decision,
1865
- resolvedAt: new Date().toISOString(),
1866
- resolution: options.resolution ?? null,
1867
- source: options.source ?? gate.source,
1868
- };
1869
- this.applyAxState({ ...oldAxState, approvalGates: replaceById(oldAxState.approvalGates, resolved) });
1870
- const applied = this.getAxState();
1871
- this.scheduleSave();
1872
- this.notifyChange('ax');
1873
- this.recordMutation({
1874
- operationType: 'resolveApproval',
1875
- description: `Resolved approval ${id} -> ${decision}`,
1876
- forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
1877
- inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
1878
- });
1879
- return applied.approvalGates.find((g) => g.id === id) ?? null;
1788
+ return this.ax.resolveApproval(id, decision, options);
1880
1789
  }
1881
1790
 
1882
1791
  // ── Review annotations (canvas-bound) ─────────────────────────────
1883
1792
  getReviewAnnotations(): PmxAxReviewAnnotation[] {
1884
- return this.getAxState().reviewAnnotations;
1793
+ return this.ax.getReviewAnnotations();
1885
1794
  }
1886
1795
 
1887
1796
  addReviewAnnotation(
@@ -1897,33 +1806,7 @@ class CanvasStateManager {
1897
1806
  },
1898
1807
  options: { source?: PmxAxSource } = {},
1899
1808
  ): PmxAxReviewAnnotation | null {
1900
- // Validate the node anchor up front. A node-anchored review whose nodeId is
1901
- // missing or unknown would otherwise be silently dropped by
1902
- // normalizeAxForCurrentNodes after apply, yet still returned as a phantom
1903
- // success object — false success / silent data loss. Reject instead so the
1904
- // HTTP/MCP layers surface ok:false / 4xx.
1905
- // Context-aware default: only fall back to a node anchor when a usable nodeId
1906
- // is present; otherwise treat it as an unanchored (body-only) note so a
1907
- // `{ body }`-only annotation succeeds (anchorType is documented optional).
1908
- const anchorType = input.anchorType ?? (typeof input.nodeId === 'string' && input.nodeId ? 'node' : 'file');
1909
- // An EXPLICIT node anchor still requires a real nodeId — reject a phantom
1910
- // node-anchored review rather than silently dropping it post-apply.
1911
- if (anchorType === 'node' && (typeof input.nodeId !== 'string' || !this.currentNodeIdSet().has(input.nodeId))) {
1912
- return null;
1913
- }
1914
- const oldAxState = this.getAxState();
1915
- const annotation = createAxReviewAnnotation(input, options.source ?? 'api');
1916
- this.applyAxState({ ...oldAxState, reviewAnnotations: [...oldAxState.reviewAnnotations, annotation] });
1917
- const applied = this.getAxState();
1918
- this.scheduleSave();
1919
- this.notifyChange('ax');
1920
- this.recordMutation({
1921
- operationType: 'addReviewAnnotation',
1922
- description: `Added review ${annotation.kind} (${annotation.severity})`,
1923
- forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
1924
- inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
1925
- });
1926
- return applied.reviewAnnotations.find((r) => r.id === annotation.id) ?? annotation;
1809
+ return this.ax.addReviewAnnotation(input, options);
1927
1810
  }
1928
1811
 
1929
1812
  updateReviewAnnotation(
@@ -1931,57 +1814,23 @@ class CanvasStateManager {
1931
1814
  patch: { body?: string; status?: PmxAxReviewStatus; severity?: PmxAxReviewSeverity; kind?: PmxAxReviewKind },
1932
1815
  options: { source?: PmxAxSource } = {},
1933
1816
  ): PmxAxReviewAnnotation | null {
1934
- const oldAxState = this.getAxState();
1935
- const existing = oldAxState.reviewAnnotations.find((r) => r.id === id);
1936
- if (!existing) return null;
1937
- const merged: PmxAxReviewAnnotation = {
1938
- ...existing,
1939
- ...(patch.body !== undefined ? { body: patch.body } : {}),
1940
- ...(patch.status !== undefined ? { status: patch.status } : {}),
1941
- ...(patch.severity !== undefined ? { severity: patch.severity } : {}),
1942
- ...(patch.kind !== undefined ? { kind: patch.kind } : {}),
1943
- updatedAt: new Date().toISOString(),
1944
- source: options.source ?? existing.source,
1945
- };
1946
- this.applyAxState({ ...oldAxState, reviewAnnotations: replaceById(oldAxState.reviewAnnotations, merged) });
1947
- const applied = this.getAxState();
1948
- this.scheduleSave();
1949
- this.notifyChange('ax');
1950
- this.recordMutation({
1951
- operationType: 'updateReviewAnnotation',
1952
- description: `Updated review ${id}`,
1953
- forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
1954
- inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
1955
- });
1956
- return applied.reviewAnnotations.find((r) => r.id === id) ?? null;
1817
+ return this.ax.updateReviewAnnotation(id, patch, options);
1957
1818
  }
1958
1819
 
1959
1820
  // ── Host capability (own table; reported by adapters) ─────────────
1960
1821
  getHostCapability(): PmxAxHostCapability | null {
1961
- return this._axHostCapability;
1822
+ return this.ax.getHostCapability();
1962
1823
  }
1963
1824
 
1964
1825
  getElicitations(): PmxAxElicitation[] {
1965
- return this.getAxState().elicitations;
1826
+ return this.ax.getElicitations();
1966
1827
  }
1967
1828
 
1968
1829
  requestElicitation(
1969
1830
  input: { prompt: string; fields?: string[]; nodeIds?: string[] },
1970
1831
  options: { source?: PmxAxSource } = {},
1971
1832
  ): PmxAxElicitation {
1972
- const oldAxState = this.getAxState();
1973
- const elicitation = createAxElicitation(input, options.source ?? 'api', this.currentNodeIdSet());
1974
- this.applyAxState({ ...oldAxState, elicitations: [...oldAxState.elicitations, elicitation] });
1975
- const applied = this.getAxState();
1976
- this.scheduleSave();
1977
- this.notifyChange('ax');
1978
- this.recordMutation({
1979
- operationType: 'requestElicitation',
1980
- description: `Requested elicitation "${elicitation.prompt}"`,
1981
- forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
1982
- inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
1983
- });
1984
- return applied.elicitations.find((e) => e.id === elicitation.id) ?? elicitation;
1833
+ return this.ax.requestElicitation(input, options);
1985
1834
  }
1986
1835
 
1987
1836
  respondElicitation(
@@ -1989,50 +1838,18 @@ class CanvasStateManager {
1989
1838
  response: Record<string, unknown>,
1990
1839
  options: { source?: PmxAxSource } = {},
1991
1840
  ): PmxAxElicitation | null {
1992
- const oldAxState = this.getAxState();
1993
- const existing = oldAxState.elicitations.find((e) => e.id === id);
1994
- if (!existing || existing.status !== 'pending') return null;
1995
- const merged: PmxAxElicitation = {
1996
- ...existing,
1997
- status: 'answered',
1998
- response,
1999
- resolvedAt: new Date().toISOString(),
2000
- source: options.source ?? existing.source,
2001
- };
2002
- this.applyAxState({ ...oldAxState, elicitations: replaceById(oldAxState.elicitations, merged) });
2003
- const applied = this.getAxState();
2004
- this.scheduleSave();
2005
- this.notifyChange('ax');
2006
- this.recordMutation({
2007
- operationType: 'respondElicitation',
2008
- description: `Answered elicitation ${id}`,
2009
- forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
2010
- inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
2011
- });
2012
- return applied.elicitations.find((e) => e.id === id) ?? null;
1841
+ return this.ax.respondElicitation(id, response, options);
2013
1842
  }
2014
1843
 
2015
1844
  getModeRequests(): PmxAxModeRequest[] {
2016
- return this.getAxState().modeRequests;
1845
+ return this.ax.getModeRequests();
2017
1846
  }
2018
1847
 
2019
1848
  requestMode(
2020
1849
  input: { mode: PmxAxMode; reason?: string | null; nodeIds?: string[] },
2021
1850
  options: { source?: PmxAxSource } = {},
2022
1851
  ): PmxAxModeRequest {
2023
- const oldAxState = this.getAxState();
2024
- const request = createAxModeRequest(input, options.source ?? 'api', this.currentNodeIdSet());
2025
- this.applyAxState({ ...oldAxState, modeRequests: [...oldAxState.modeRequests, request] });
2026
- const applied = this.getAxState();
2027
- this.scheduleSave();
2028
- this.notifyChange('ax');
2029
- this.recordMutation({
2030
- operationType: 'requestMode',
2031
- description: `Requested mode "${request.mode}"`,
2032
- forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
2033
- inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
2034
- });
2035
- return applied.modeRequests.find((m) => m.id === request.id) ?? request;
1852
+ return this.ax.requestMode(input, options);
2036
1853
  }
2037
1854
 
2038
1855
  resolveModeRequest(
@@ -2040,98 +1857,45 @@ class CanvasStateManager {
2040
1857
  decision: 'approved' | 'rejected',
2041
1858
  options: { resolution?: string; source?: PmxAxSource } = {},
2042
1859
  ): PmxAxModeRequest | null {
2043
- const oldAxState = this.getAxState();
2044
- const existing = oldAxState.modeRequests.find((m) => m.id === id);
2045
- if (!existing || existing.status !== 'pending') return null;
2046
- const merged: PmxAxModeRequest = {
2047
- ...existing,
2048
- status: decision,
2049
- resolvedAt: new Date().toISOString(),
2050
- resolution: options.resolution ?? null,
2051
- source: options.source ?? existing.source,
2052
- };
2053
- this.applyAxState({ ...oldAxState, modeRequests: replaceById(oldAxState.modeRequests, merged) });
2054
- const applied = this.getAxState();
2055
- this.scheduleSave();
2056
- this.notifyChange('ax');
2057
- this.recordMutation({
2058
- operationType: 'resolveModeRequest',
2059
- description: `Resolved mode request ${id} -> ${decision}`,
2060
- forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
2061
- inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
2062
- });
2063
- return applied.modeRequests.find((m) => m.id === id) ?? null;
1860
+ return this.ax.resolveModeRequest(id, decision, options);
2064
1861
  }
2065
1862
 
2066
1863
  // ── Single-item AX readers (canvas-bound; for the blocking-wait endpoints) ──
2067
1864
  getApproval(id: string): PmxAxApprovalGate | null {
2068
- return this.getAxState().approvalGates.find((g) => g.id === id) ?? null;
1865
+ return this.ax.getApproval(id);
2069
1866
  }
2070
1867
 
2071
1868
  getElicitation(id: string): PmxAxElicitation | null {
2072
- return this.getAxState().elicitations.find((e) => e.id === id) ?? null;
1869
+ return this.ax.getElicitation(id);
2073
1870
  }
2074
1871
 
2075
1872
  getModeRequest(id: string): PmxAxModeRequest | null {
2076
- return this.getAxState().modeRequests.find((m) => m.id === id) ?? null;
1873
+ return this.ax.getModeRequest(id);
2077
1874
  }
2078
1875
 
2079
1876
  getCommandRegistry(): PmxAxCommandDescriptor[] {
2080
- return listAxCommands();
1877
+ return this.ax.getCommandRegistry();
2081
1878
  }
2082
1879
 
2083
1880
  /** Invoke a registry-gated PMX command intent — records a timeline event (no execution). */
2084
1881
  invokeCommand(name: string, args: Record<string, unknown> | null = null, options: { source?: PmxAxSource } = {}): PmxAxEvent | null {
2085
- if (!isAxCommand(name)) return null;
2086
- return this.recordAxEvent(
2087
- { kind: 'command', summary: name, detail: AX_COMMAND_REGISTRY[name].description, data: { command: name, ...(args ? { args } : {}) } },
2088
- options,
2089
- );
1882
+ return this.ax.invokeCommand(name, args, options);
2090
1883
  }
2091
1884
 
2092
1885
  getPolicy(): PmxAxPolicy {
2093
- return this.getAxState().policy;
1886
+ return this.ax.getPolicy();
2094
1887
  }
2095
1888
 
2096
1889
  /** Merge a declarative tool/prompt policy patch (canvas-bound, snapshotted). */
2097
1890
  setPolicy(
2098
1891
  patch: { tools?: Partial<PmxAxPolicy['tools']>; prompt?: Partial<PmxAxPolicy['prompt']> },
2099
- _options: { source?: PmxAxSource } = {},
1892
+ options: { source?: PmxAxSource } = {},
2100
1893
  ): PmxAxPolicy {
2101
- const oldAxState = this.getAxState();
2102
- const merged = normalizeAxPolicy({
2103
- tools: { ...oldAxState.policy.tools, ...(patch.tools ?? {}) },
2104
- prompt: { ...oldAxState.policy.prompt, ...(patch.prompt ?? {}) },
2105
- });
2106
- this.applyAxState({ ...oldAxState, policy: merged });
2107
- const applied = this.getAxState();
2108
- this.scheduleSave();
2109
- this.notifyChange('ax');
2110
- this.recordMutation({
2111
- operationType: 'setPolicy',
2112
- description: 'Updated AX policy',
2113
- forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
2114
- inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
2115
- });
2116
- return applied.policy;
1894
+ return this.ax.setPolicy(patch, options);
2117
1895
  }
2118
1896
 
2119
- setHostCapability(input: unknown, _options: { source?: PmxAxSource } = {}): PmxAxHostCapability {
2120
- const cap = normalizeAxHostCapability(
2121
- isRecord(input)
2122
- ? { ...input, reportedAt: new Date().toISOString() }
2123
- : { reportedAt: new Date().toISOString() },
2124
- ) ?? createEmptyAxHostCapability();
2125
- this._axHostCapability = cap;
2126
- if (this._db) {
2127
- try {
2128
- upsertAxHostCapabilityToDB(this._db, cap);
2129
- } catch (error) {
2130
- logCanvasStateWarning('save host capability failed', error, {});
2131
- }
2132
- }
2133
- this.notifyChange('ax');
2134
- return cap;
1897
+ setHostCapability(input: unknown, options: { source?: PmxAxSource } = {}): PmxAxHostCapability {
1898
+ return this.ax.setHostCapability(input, options);
2135
1899
  }
2136
1900
 
2137
1901
  // ── Timeline (DB-direct; NOT in _axState; NOT history-recorded) ───
@@ -2139,77 +1903,24 @@ class CanvasStateManager {
2139
1903
  input: { kind: PmxAxEventKind; summary: string; detail?: string | null; nodeIds?: string[]; data?: Record<string, unknown> | null },
2140
1904
  options: { source?: PmxAxSource } = {},
2141
1905
  ): PmxAxEvent {
2142
- const draft = createAxEvent(input, options.source ?? 'api');
2143
- if (this._db) {
2144
- try {
2145
- const ev = appendAxEventToDB(this._db, draft);
2146
- this.notifyChange('ax-timeline');
2147
- return ev;
2148
- } catch (error) {
2149
- logCanvasStateWarning('record ax event failed', error, { id: draft.id });
2150
- }
2151
- }
2152
- this.notifyChange('ax-timeline');
2153
- return { ...draft, seq: 0 };
1906
+ return this.ax.recordAxEvent(input, options);
2154
1907
  }
2155
1908
 
2156
1909
  addEvidence(
2157
1910
  input: { kind: PmxAxEvidenceKind; title: string; body?: string | null; ref?: string | null; nodeIds?: string[]; data?: Record<string, unknown> | null },
2158
1911
  options: { source?: PmxAxSource } = {},
2159
1912
  ): PmxAxEvidence {
2160
- const draft = createAxEvidence(input, options.source ?? 'api');
2161
- if (this._db) {
2162
- try {
2163
- const ev = appendAxEvidenceToDB(this._db, draft);
2164
- this.notifyChange('ax-timeline');
2165
- return ev;
2166
- } catch (error) {
2167
- logCanvasStateWarning('add evidence failed', error, { id: draft.id });
2168
- }
2169
- }
2170
- this.notifyChange('ax-timeline');
2171
- return { ...draft, seq: 0 };
1913
+ return this.ax.addEvidence(input, options);
2172
1914
  }
2173
1915
 
2174
1916
  recordSteeringMessage(message: string, options: { source?: PmxAxSource } = {}): PmxAxSteeringMessage {
2175
- const draft = createAxSteeringMessage(message, options.source ?? 'api');
2176
- if (this._db) {
2177
- try {
2178
- const s = appendAxSteeringToDB(this._db, draft);
2179
- this.notifyChange('ax-timeline');
2180
- return s;
2181
- } catch (error) {
2182
- logCanvasStateWarning('record steering failed', error, { id: draft.id });
2183
- }
2184
- }
2185
- this.notifyChange('ax-timeline');
2186
- return { ...draft, seq: 0 };
1917
+ return this.ax.recordSteeringMessage(message, options);
2187
1918
  }
2188
1919
 
2189
1920
  markSteeringDelivered(id: string): boolean {
2190
- if (!this._db) return false;
2191
- try {
2192
- const ok = markAxSteeringDeliveredInDB(this._db, id);
2193
- if (ok) this.notifyChange('ax-timeline');
2194
- return ok;
2195
- } catch (error) {
2196
- logCanvasStateWarning('mark steering delivered failed', error, { id });
2197
- return false;
2198
- }
1921
+ return this.ax.markSteeringDelivered(id);
2199
1922
  }
2200
1923
 
2201
- /**
2202
- * Ingest a normalized agent activity (a tool/session event a harness forwards)
2203
- * and apply kind-driven board reactions, so the agent's real work flows back into
2204
- * the board without it remembering to push each item (report primitive A — makes
2205
- * AX bidirectional). Always records a timeline event; then, unless the caller
2206
- * overrides/suppresses via `reactions`, applies defaults by kind/outcome:
2207
- * • failure | error | outcome==='failure' → work item (blocked) + review
2208
- * (finding/error, anchored to a valid nodeId else the `ref` file) + evidence (logs)
2209
- * • tool-result + outcome==='success' → evidence (tool-result)
2210
- * • everything else (tool-start, session-*, command, note) → event only
2211
- * A reaction value of `false` suppresses it; an object overrides its fields/forces it on.
2212
- */
2213
1924
  ingestActivity(
2214
1925
  input: {
2215
1926
  kind: PmxAxActivityKind;
@@ -2227,112 +1938,39 @@ class CanvasStateManager {
2227
1938
  },
2228
1939
  options: { source?: PmxAxSource } = {},
2229
1940
  ): { event: PmxAxEvent; workItem: PmxAxWorkItem | null; evidence: PmxAxEvidence | null; review: PmxAxReviewAnnotation | null } {
2230
- const source = options.source ?? 'api';
2231
- const summary = input.summary ?? input.title;
2232
- const isFailure = input.kind === 'failure' || input.kind === 'error' || input.outcome === 'failure';
2233
- const isToolSuccess = input.kind === 'tool-result' && input.outcome === 'success';
2234
- const nodeIds = input.nodeIds ?? [];
2235
- const anchorNodeId = nodeIds.find((n) => this.nodes.has(n)) ?? null;
2236
-
2237
- // (1) Always record the activity on the timeline (precise kind on data.activityKind).
2238
- const event = this.recordAxEvent(
2239
- {
2240
- kind: mapAxActivityKindToEventKind(input.kind),
2241
- summary: input.title,
2242
- detail: input.summary ?? null,
2243
- nodeIds,
2244
- // Caller data first so the canonical fields always win — a malformed/hostile
2245
- // payload can't overwrite activityKind/outcome/ref (which the docstring +
2246
- // reaction logic treat as authoritative).
2247
- data: {
2248
- ...(input.data ?? {}),
2249
- activityKind: input.kind,
2250
- ...(input.outcome ? { outcome: input.outcome } : {}),
2251
- ...(input.ref ? { ref: input.ref } : {}),
2252
- },
2253
- },
2254
- { source },
2255
- );
2256
-
2257
- // (2) Resolve reactions: kind-driven defaults, overridable per call.
2258
- const r = input.reactions ?? {};
2259
- const wantWorkItem = r.workItem === false ? null : (r.workItem ?? (isFailure ? {} : null));
2260
- const wantEvidence = r.evidence === false
2261
- ? null
2262
- : (r.evidence ?? (isFailure ? { kind: 'logs' as PmxAxEvidenceKind } : isToolSuccess ? { kind: 'tool-result' as PmxAxEvidenceKind } : null));
2263
- const wantReview = r.review === false ? null : (r.review ?? (isFailure ? {} : null));
2264
-
2265
- let workItem: PmxAxWorkItem | null = null;
2266
- if (wantWorkItem) {
2267
- workItem = this.addWorkItem(
2268
- { title: input.title, status: wantWorkItem.status ?? 'blocked', detail: wantWorkItem.detail ?? summary, nodeIds },
2269
- { source },
2270
- );
2271
- }
2272
-
2273
- let evidence: PmxAxEvidence | null = null;
2274
- if (wantEvidence) {
2275
- evidence = this.addEvidence(
2276
- { kind: wantEvidence.kind ?? 'logs', title: input.title, body: wantEvidence.body ?? input.summary ?? null, ref: input.ref ?? null, nodeIds },
2277
- { source },
2278
- );
2279
- }
2280
-
2281
- let review: PmxAxReviewAnnotation | null = null;
2282
- if (wantReview) {
2283
- const reviewNodeId = wantReview.nodeId ?? anchorNodeId;
2284
- // addReviewAnnotation returns null on a bad node anchor — that just skips the
2285
- // review; it never fails the whole ingest (the event + other reactions stand).
2286
- review = this.addReviewAnnotation(
2287
- {
2288
- body: summary,
2289
- kind: wantReview.kind ?? 'finding',
2290
- severity: wantReview.severity ?? 'error',
2291
- ...(wantReview.anchorType ? { anchorType: wantReview.anchorType } : {}),
2292
- ...(reviewNodeId ? { nodeId: reviewNodeId } : {}),
2293
- ...(input.ref ? { file: input.ref } : {}),
2294
- },
2295
- { source },
2296
- );
2297
- }
2298
-
2299
- return { event, workItem, evidence, review };
1941
+ return this.ax.ingestActivity(input, options);
2300
1942
  }
2301
1943
 
2302
1944
  getAxEvents(q: AxTimelineQuery = {}): PmxAxEvent[] {
2303
- return this._db ? loadAxEventsFromDB(this._db, q) : [];
1945
+ return this.ax.getAxEvents(q);
2304
1946
  }
2305
1947
 
2306
1948
  getAxEvidence(q: AxTimelineQuery = {}): PmxAxEvidence[] {
2307
- return this._db ? loadAxEvidenceFromDB(this._db, q) : [];
1949
+ return this.ax.getAxEvidence(q);
2308
1950
  }
2309
1951
 
2310
1952
  getAxSteering(q: AxTimelineQuery & { onlyPending?: boolean } = {}): PmxAxSteeringMessage[] {
2311
- return this._db ? loadAxSteeringFromDB(this._db, q) : [];
1953
+ return this.ax.getAxSteering(q);
2312
1954
  }
2313
1955
 
2314
- /**
2315
- * Undelivered steering for a consumer (Phase 4 delivery). Excludes messages
2316
- * whose source equals the consumer to prevent delivery loops (e.g. Copilot
2317
- * should not be handed back steering it originated).
2318
- */
2319
1956
  getPendingSteering(options: { consumer?: string; limit?: number } = {}): PmxAxSteeringMessage[] {
2320
- return this._db ? loadPendingAxSteeringFromDB(this._db, options) : [];
1957
+ return this.ax.getPendingSteering(options);
1958
+ }
1959
+
1960
+ getPendingSteeringForContext(options: { consumer?: string; limit?: number } = {}): PmxAxSteeringMessage[] {
1961
+ return this.ax.getPendingSteeringForContext(options);
1962
+ }
1963
+
1964
+ getPendingSteeringCount(consumer?: string): number {
1965
+ return this.ax.getPendingSteeringCount(consumer);
2321
1966
  }
2322
1967
 
2323
1968
  getAxTimelineSummary(): PmxAxTimelineSummary {
2324
- return this._db
2325
- ? loadAxTimelineSummaryFromDB(this._db)
2326
- : { recentEvents: [], recentEvidence: [], pendingSteering: [], counts: { events: 0, evidence: 0, steering: 0 } };
1969
+ return this.ax.getAxTimelineSummary();
2327
1970
  }
2328
1971
 
2329
1972
  getAxTimeline(q: AxTimelineQuery = {}): { events: PmxAxEvent[]; evidence: PmxAxEvidence[]; steering: PmxAxSteeringMessage[]; summary: PmxAxTimelineSummary } {
2330
- return {
2331
- events: this.getAxEvents(q),
2332
- evidence: this.getAxEvidence(q),
2333
- steering: this.getAxSteering(q),
2334
- summary: this.getAxTimelineSummary(),
2335
- };
1973
+ return this.ax.getAxTimeline(q);
2336
1974
  }
2337
1975
 
2338
1976
  setContextPins(nodeIds: string[]): void {
@@ -2469,7 +2107,7 @@ class CanvasStateManager {
2469
2107
  // Clears canvas-bound AX state (focus, work items, approvals, review annotations).
2470
2108
  // Timeline tables (ax_events/ax_evidence/ax_steering) and host capability are
2471
2109
  // deliberately retained per the AX state-partition policy.
2472
- this._axState = createEmptyAxState();
2110
+ this.ax.resetCanvasBound();
2473
2111
  this._viewport = { x: 0, y: 0, scale: 1 };
2474
2112
  this.scheduleSave();
2475
2113
  this.notifyChange('nodes');
@@ -2484,7 +2122,7 @@ class CanvasStateManager {
2484
2122
  for (const e of oldEdges) this.addEdge(structuredClone(e));
2485
2123
  for (const annotation of oldAnnotations) this.addAnnotation(structuredClone(annotation));
2486
2124
  this.setContextPins(oldPins);
2487
- this.applyAxState(oldAxState);
2125
+ this.ax.applyPersistedAx(oldAxState);
2488
2126
  this.setViewport(oldViewport);
2489
2127
  this.notifyChange('ax');
2490
2128
  }),