pmx-canvas 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/canvas/index.js +2 -2
  3. package/dist/types/cli/daemon.d.ts +74 -0
  4. package/dist/types/cli/watch.d.ts +2 -2
  5. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  6. package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
  7. package/dist/types/client/canvas/Minimap.d.ts +1 -1
  8. package/dist/types/client/nodes/FileNode.d.ts +1 -1
  9. package/dist/types/client/nodes/ImageNode.d.ts +1 -1
  10. package/dist/types/client/nodes/InlineFormatBar.d.ts +1 -1
  11. package/dist/types/client/nodes/MarkdownNode.d.ts +1 -1
  12. package/dist/types/client/nodes/PromptNode.d.ts +1 -1
  13. package/dist/types/client/nodes/ResponseNode.d.ts +1 -1
  14. package/dist/types/server/canvas-schema.d.ts +1 -1
  15. package/dist/types/server/html-primitives.d.ts +1 -1
  16. package/dist/types/server/index.d.ts +4 -4
  17. package/dist/types/server/operations/index.d.ts +1 -1
  18. package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
  19. package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
  20. package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
  21. package/docs/http-api.md +28 -0
  22. package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
  23. package/docs/tech-debt-assessment-2026-07.md +2 -2
  24. package/package.json +5 -2
  25. package/skills/pmx-canvas/SKILL.md +3 -1
  26. package/skills/pmx-canvas/references/full-reference.md +10 -3
  27. package/src/cli/agent.ts +1861 -1548
  28. package/src/cli/daemon.ts +460 -0
  29. package/src/cli/index.ts +63 -326
  30. package/src/cli/watch.ts +2 -10
  31. package/src/client/App.tsx +48 -46
  32. package/src/client/canvas/AttentionHistory.tsx +11 -1
  33. package/src/client/canvas/CanvasNode.tsx +41 -29
  34. package/src/client/canvas/CanvasViewport.tsx +101 -66
  35. package/src/client/canvas/CommandPalette.tsx +61 -27
  36. package/src/client/canvas/ContextMenu.tsx +13 -20
  37. package/src/client/canvas/ContextPinBar.tsx +1 -5
  38. package/src/client/canvas/ContextPinHud.tsx +1 -6
  39. package/src/client/canvas/DockedNode.tsx +4 -4
  40. package/src/client/canvas/EdgeLayer.tsx +37 -36
  41. package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
  42. package/src/client/canvas/FocusFieldLayer.tsx +20 -22
  43. package/src/client/canvas/IntentLayer.tsx +31 -14
  44. package/src/client/canvas/Minimap.tsx +11 -16
  45. package/src/client/canvas/SelectionBar.tsx +4 -11
  46. package/src/client/canvas/ShortcutOverlay.tsx +3 -1
  47. package/src/client/canvas/SnapshotPanel.tsx +77 -95
  48. package/src/client/canvas/auto-fit.ts +15 -14
  49. package/src/client/canvas/snap-guides.ts +12 -12
  50. package/src/client/canvas/use-node-resize.ts +1 -5
  51. package/src/client/canvas/use-pan-zoom.ts +25 -26
  52. package/src/client/ext-app/bridge.ts +3 -12
  53. package/src/client/icons.tsx +63 -20
  54. package/src/client/nodes/ContextNode.tsx +14 -25
  55. package/src/client/nodes/ExtAppFrame.tsx +60 -39
  56. package/src/client/nodes/FileNode.tsx +74 -62
  57. package/src/client/nodes/GroupNode.tsx +4 -6
  58. package/src/client/nodes/HtmlNode.tsx +76 -46
  59. package/src/client/nodes/ImageNode.tsx +18 -27
  60. package/src/client/nodes/InlineFormatBar.tsx +4 -21
  61. package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
  62. package/src/client/nodes/LedgerNode.tsx +10 -4
  63. package/src/client/nodes/MarkdownNode.tsx +3 -10
  64. package/src/client/nodes/McpAppNode.tsx +26 -22
  65. package/src/client/nodes/MdFormatBar.tsx +10 -7
  66. package/src/client/nodes/PromptNode.tsx +23 -51
  67. package/src/client/nodes/ResponseNode.tsx +3 -13
  68. package/src/client/nodes/StatusNode.tsx +5 -9
  69. package/src/client/nodes/StatusSummary.tsx +2 -8
  70. package/src/client/nodes/WebpageNode.tsx +20 -14
  71. package/src/client/nodes/iframe-document-url.ts +25 -16
  72. package/src/client/nodes/image-warnings.ts +1 -7
  73. package/src/client/nodes/md-format.ts +20 -5
  74. package/src/client/state/attention-bridge.ts +4 -9
  75. package/src/client/state/attention-store.ts +1 -7
  76. package/src/client/state/canvas-store.ts +52 -36
  77. package/src/client/state/intent-bridge.ts +176 -112
  78. package/src/client/state/intent-store.ts +4 -1
  79. package/src/client/state/sse-bridge.ts +53 -70
  80. package/src/json-render/catalog.ts +12 -16
  81. package/src/json-render/charts/components.tsx +16 -20
  82. package/src/json-render/charts/extra-components.tsx +8 -16
  83. package/src/json-render/charts/extra-definitions.ts +1 -2
  84. package/src/json-render/charts/tufte-components.tsx +37 -20
  85. package/src/json-render/charts/tufte-definitions.ts +8 -2
  86. package/src/json-render/renderer/index.tsx +42 -22
  87. package/src/json-render/schema.ts +6 -3
  88. package/src/json-render/server.ts +33 -39
  89. package/src/mcp/canvas-access.ts +35 -21
  90. package/src/mcp/server.ts +132 -70
  91. package/src/server/agent-context.ts +63 -36
  92. package/src/server/ax-context.ts +7 -5
  93. package/src/server/ax-interaction.ts +176 -43
  94. package/src/server/ax-state-manager.ts +182 -39
  95. package/src/server/ax-state.ts +142 -47
  96. package/src/server/canvas-db.ts +213 -95
  97. package/src/server/canvas-operations.ts +177 -120
  98. package/src/server/canvas-provenance.ts +1 -4
  99. package/src/server/canvas-schema.ts +454 -73
  100. package/src/server/canvas-serialization.ts +27 -35
  101. package/src/server/canvas-state.ts +150 -58
  102. package/src/server/chart-template.ts +4 -5
  103. package/src/server/code-graph.ts +19 -6
  104. package/src/server/diagram-presets.ts +28 -29
  105. package/src/server/ext-app-lookup.ts +3 -12
  106. package/src/server/html-node-summary.ts +19 -10
  107. package/src/server/html-primitives.ts +326 -97
  108. package/src/server/html-surface.ts +6 -9
  109. package/src/server/image-source.ts +6 -4
  110. package/src/server/index.ts +320 -217
  111. package/src/server/intent-registry.ts +2 -5
  112. package/src/server/mcp-app-candidate.ts +5 -10
  113. package/src/server/mcp-app-host.ts +14 -38
  114. package/src/server/mcp-app-runtime.ts +12 -20
  115. package/src/server/mutation-history.ts +15 -5
  116. package/src/server/operations/composites.ts +1 -3
  117. package/src/server/operations/http.ts +2 -3
  118. package/src/server/operations/index.ts +7 -1
  119. package/src/server/operations/invoker.ts +4 -3
  120. package/src/server/operations/mcp.ts +22 -30
  121. package/src/server/operations/ops/annotation.ts +122 -10
  122. package/src/server/operations/ops/app.ts +98 -73
  123. package/src/server/operations/ops/ax-await.ts +17 -10
  124. package/src/server/operations/ops/ax-read.ts +347 -0
  125. package/src/server/operations/ops/ax-shared.ts +2 -7
  126. package/src/server/operations/ops/ax-state.ts +32 -14
  127. package/src/server/operations/ops/ax-timeline.ts +32 -19
  128. package/src/server/operations/ops/ax-work.ts +54 -37
  129. package/src/server/operations/ops/batch.ts +39 -14
  130. package/src/server/operations/ops/canvas-wire.ts +91 -0
  131. package/src/server/operations/ops/edges.ts +37 -25
  132. package/src/server/operations/ops/ext-app.ts +346 -0
  133. package/src/server/operations/ops/groups.ts +49 -20
  134. package/src/server/operations/ops/intent.ts +18 -12
  135. package/src/server/operations/ops/json-render.ts +239 -98
  136. package/src/server/operations/ops/nodes.ts +298 -109
  137. package/src/server/operations/ops/query.ts +46 -28
  138. package/src/server/operations/ops/snapshots.ts +35 -26
  139. package/src/server/operations/ops/validate.ts +2 -1
  140. package/src/server/operations/ops/viewport.ts +60 -16
  141. package/src/server/operations/ops/webview.ts +44 -18
  142. package/src/server/operations/registry.ts +2 -3
  143. package/src/server/operations/types.ts +7 -5
  144. package/src/server/operations/webview-runner.ts +1 -3
  145. package/src/server/placement.ts +8 -18
  146. package/src/server/server.ts +108 -1027
  147. package/src/server/spatial-analysis.ts +39 -25
  148. package/src/server/trace-manager.ts +3 -8
  149. package/src/server/web-artifacts.ts +23 -27
  150. package/src/server/webpage-node.ts +5 -13
  151. package/src/shared/auto-arrange.ts +12 -5
  152. package/src/shared/content-height-reporter.ts +8 -6
  153. package/src/shared/ext-app-tool-result.ts +2 -6
  154. package/src/shared/placement.ts +1 -4
  155. package/src/shared/semantic-attention.ts +39 -37
  156. package/src/shared/surface.ts +8 -4
@@ -358,7 +358,10 @@ export function saveStateToDB(db: Database, state: PersistedCanvasState): void {
358
358
  insertPin.run(pinId);
359
359
  }
360
360
 
361
- db.run('INSERT INTO ax_state (key, value) VALUES (?, ?)', ['state', JSON.stringify(state.ax ?? createEmptyAxState())]);
361
+ db.run('INSERT INTO ax_state (key, value) VALUES (?, ?)', [
362
+ 'state',
363
+ JSON.stringify(state.ax ?? createEmptyAxState()),
364
+ ]);
362
365
  });
363
366
 
364
367
  transaction();
@@ -366,14 +369,14 @@ export function saveStateToDB(db: Database, state: PersistedCanvasState): void {
366
369
 
367
370
  /** Check if the DB has been populated with canvas state at least once. */
368
371
  export function isDbPopulated(db: Database): boolean {
369
- const row = db.query<{ value: string }, [string]>(
370
- 'SELECT value FROM meta WHERE key = ?',
371
- ).get('state_populated');
372
+ const row = db.query<{ value: string }, [string]>('SELECT value FROM meta WHERE key = ?').get('state_populated');
372
373
  return row?.value === '1';
373
374
  }
374
375
 
375
376
  export function loadStateFromDB(db: Database): PersistedCanvasState | null {
376
- const schemaVersion = db.query<{ value: string }, [string]>('SELECT value FROM meta WHERE key = ?').get('schema_version');
377
+ const schemaVersion = db
378
+ .query<{ value: string }, [string]>('SELECT value FROM meta WHERE key = ?')
379
+ .get('schema_version');
377
380
  if (!schemaVersion) return null;
378
381
 
379
382
  // Load viewport
@@ -464,7 +467,9 @@ export function loadStateFromDB(db: Database): PersistedCanvasState | null {
464
467
  }));
465
468
 
466
469
  // Load context pins
467
- interface PinRow { node_id: string }
470
+ interface PinRow {
471
+ node_id: string;
472
+ }
468
473
  const pinRows = db.query<PinRow, []>('SELECT node_id FROM context_pins').all();
469
474
  const contextPins = pinRows.map((row) => row.node_id);
470
475
 
@@ -484,23 +489,38 @@ export function loadStateFromDB(db: Database): PersistedCanvasState | null {
484
489
 
485
490
  // ── Snapshot Persistence ────────────────────────────────────────
486
491
 
487
- export function saveSnapshotToDB(
488
- db: Database,
489
- snapshot: CanvasSnapshot,
490
- state: PersistedCanvasState,
491
- ): void {
492
+ export function saveSnapshotToDB(db: Database, snapshot: CanvasSnapshot, state: PersistedCanvasState): void {
492
493
  const transaction = db.transaction(() => {
493
494
  // Insert snapshot metadata
494
- db.run(
495
- 'INSERT INTO snapshots (id, name, created_at, node_count, edge_count) VALUES (?, ?, ?, ?, ?)',
496
- [snapshot.id, snapshot.name, snapshot.createdAt, state.nodes.length, state.edges.length],
497
- );
495
+ db.run('INSERT INTO snapshots (id, name, created_at, node_count, edge_count) VALUES (?, ?, ?, ?, ?)', [
496
+ snapshot.id,
497
+ snapshot.name,
498
+ snapshot.createdAt,
499
+ state.nodes.length,
500
+ state.edges.length,
501
+ ]);
498
502
 
499
503
  // Insert snapshot viewport meta
500
- db.run('INSERT INTO snapshot_meta (snapshot_id, key, value) VALUES (?, ?, ?)', [snapshot.id, 'viewport_x', String(state.viewport.x)]);
501
- db.run('INSERT INTO snapshot_meta (snapshot_id, key, value) VALUES (?, ?, ?)', [snapshot.id, 'viewport_y', String(state.viewport.y)]);
502
- db.run('INSERT INTO snapshot_meta (snapshot_id, key, value) VALUES (?, ?, ?)', [snapshot.id, 'viewport_scale', String(state.viewport.scale)]);
503
- db.run('INSERT INTO snapshot_meta (snapshot_id, key, value) VALUES (?, ?, ?)', [snapshot.id, 'ax_state', JSON.stringify(state.ax ?? createEmptyAxState())]);
504
+ db.run('INSERT INTO snapshot_meta (snapshot_id, key, value) VALUES (?, ?, ?)', [
505
+ snapshot.id,
506
+ 'viewport_x',
507
+ String(state.viewport.x),
508
+ ]);
509
+ db.run('INSERT INTO snapshot_meta (snapshot_id, key, value) VALUES (?, ?, ?)', [
510
+ snapshot.id,
511
+ 'viewport_y',
512
+ String(state.viewport.y),
513
+ ]);
514
+ db.run('INSERT INTO snapshot_meta (snapshot_id, key, value) VALUES (?, ?, ?)', [
515
+ snapshot.id,
516
+ 'viewport_scale',
517
+ String(state.viewport.scale),
518
+ ]);
519
+ db.run('INSERT INTO snapshot_meta (snapshot_id, key, value) VALUES (?, ?, ?)', [
520
+ snapshot.id,
521
+ 'ax_state',
522
+ JSON.stringify(state.ax ?? createEmptyAxState()),
523
+ ]);
504
524
 
505
525
  // Insert snapshot nodes
506
526
  const insertNode = db.prepare(
@@ -584,14 +604,12 @@ export function loadSnapshotFromDB(
584
604
  node_count: number;
585
605
  edge_count: number;
586
606
  }
587
- let snapshotRow = db.query<SnapshotRow, [string]>(
588
- 'SELECT * FROM snapshots WHERE id = ?',
589
- ).get(idOrName);
607
+ let snapshotRow = db.query<SnapshotRow, [string]>('SELECT * FROM snapshots WHERE id = ?').get(idOrName);
590
608
 
591
609
  if (!snapshotRow) {
592
- snapshotRow = db.query<SnapshotRow, [string]>(
593
- 'SELECT * FROM snapshots WHERE name = ? ORDER BY created_at DESC LIMIT 1',
594
- ).get(idOrName);
610
+ snapshotRow = db
611
+ .query<SnapshotRow, [string]>('SELECT * FROM snapshots WHERE name = ? ORDER BY created_at DESC LIMIT 1')
612
+ .get(idOrName);
595
613
  }
596
614
 
597
615
  if (!snapshotRow) return null;
@@ -605,10 +623,13 @@ export function loadSnapshotFromDB(
605
623
  };
606
624
 
607
625
  // Load snapshot viewport
608
- interface MetaRow { key: string; value: string }
609
- const metaRows = db.query<MetaRow, [string]>(
610
- 'SELECT key, value FROM snapshot_meta WHERE snapshot_id = ?',
611
- ).all(snapshotRow.id);
626
+ interface MetaRow {
627
+ key: string;
628
+ value: string;
629
+ }
630
+ const metaRows = db
631
+ .query<MetaRow, [string]>('SELECT key, value FROM snapshot_meta WHERE snapshot_id = ?')
632
+ .all(snapshotRow.id);
612
633
  const metaMap = new Map(metaRows.map((r) => [r.key, r.value]));
613
634
 
614
635
  const viewport: ViewportState = {
@@ -631,9 +652,9 @@ export function loadSnapshotFromDB(
631
652
  dock_position: string | null;
632
653
  data: string;
633
654
  }
634
- const nodeRows = db.query<NodeRow, [string]>(
635
- 'SELECT * FROM snapshot_nodes WHERE snapshot_id = ?',
636
- ).all(snapshotRow.id);
655
+ const nodeRows = db
656
+ .query<NodeRow, [string]>('SELECT * FROM snapshot_nodes WHERE snapshot_id = ?')
657
+ .all(snapshotRow.id);
637
658
  const nodes: CanvasNodeState[] = nodeRows.map((row) => ({
638
659
  id: row.id,
639
660
  type: row.type as CanvasNodeState['type'],
@@ -656,9 +677,9 @@ export function loadSnapshotFromDB(
656
677
  style: string | null;
657
678
  animated: number;
658
679
  }
659
- const edgeRows = db.query<EdgeRow, [string]>(
660
- 'SELECT * FROM snapshot_edges WHERE snapshot_id = ?',
661
- ).all(snapshotRow.id);
680
+ const edgeRows = db
681
+ .query<EdgeRow, [string]>('SELECT * FROM snapshot_edges WHERE snapshot_id = ?')
682
+ .all(snapshotRow.id);
662
683
  const edges: CanvasEdge[] = edgeRows.map((row) => ({
663
684
  id: row.id,
664
685
  from: row.from_node,
@@ -681,9 +702,9 @@ export function loadSnapshotFromDB(
681
702
  label: string | null;
682
703
  created_at: string;
683
704
  }
684
- const annotationRows = db.query<AnnotationRow, [string]>(
685
- 'SELECT * FROM snapshot_annotations WHERE snapshot_id = ?',
686
- ).all(snapshotRow.id);
705
+ const annotationRows = db
706
+ .query<AnnotationRow, [string]>('SELECT * FROM snapshot_annotations WHERE snapshot_id = ?')
707
+ .all(snapshotRow.id);
687
708
  const annotations: CanvasAnnotation[] = annotationRows.map((row) => ({
688
709
  id: row.id,
689
710
  type: row.type as CanvasAnnotation['type'],
@@ -697,10 +718,12 @@ export function loadSnapshotFromDB(
697
718
  }));
698
719
 
699
720
  // Load snapshot pins
700
- interface PinRow { node_id: string }
701
- const pinRows = db.query<PinRow, [string]>(
702
- 'SELECT node_id FROM snapshot_pins WHERE snapshot_id = ?',
703
- ).all(snapshotRow.id);
721
+ interface PinRow {
722
+ node_id: string;
723
+ }
724
+ const pinRows = db
725
+ .query<PinRow, [string]>('SELECT node_id FROM snapshot_pins WHERE snapshot_id = ?')
726
+ .all(snapshotRow.id);
704
727
  const contextPins = pinRows.map((row) => row.node_id);
705
728
 
706
729
  return {
@@ -782,27 +805,29 @@ export function deleteSnapshotFromDB(db: Database, id: string): boolean {
782
805
 
783
806
  export function writeBlobToDB(db: Database, sha256: string, jsonValue: string): number {
784
807
  const compressed = gzipSync(jsonValue);
785
- db.run(
786
- 'INSERT OR IGNORE INTO blobs (sha256, data, json_bytes) VALUES (?, ?, ?)',
787
- [sha256, compressed, Buffer.byteLength(jsonValue)],
788
- );
808
+ db.run('INSERT OR IGNORE INTO blobs (sha256, data, json_bytes) VALUES (?, ?, ?)', [
809
+ sha256,
810
+ compressed,
811
+ Buffer.byteLength(jsonValue),
812
+ ]);
789
813
  return compressed.byteLength;
790
814
  }
791
815
 
792
816
  export function readBlobFromDB(db: Database, sha256: string): string | null {
793
- interface BlobRow { data: Buffer; json_bytes: number }
794
- const row = db.query<BlobRow, [string]>(
795
- 'SELECT data, json_bytes FROM blobs WHERE sha256 = ?',
796
- ).get(sha256);
817
+ interface BlobRow {
818
+ data: Buffer;
819
+ json_bytes: number;
820
+ }
821
+ const row = db.query<BlobRow, [string]>('SELECT data, json_bytes FROM blobs WHERE sha256 = ?').get(sha256);
797
822
  if (!row) return null;
798
823
  return gunzipSync(row.data).toString('utf-8');
799
824
  }
800
825
 
801
826
  export function hasBlobInDB(db: Database, sha256: string): boolean {
802
- interface CountRow { c: number }
803
- const row = db.query<CountRow, [string]>(
804
- 'SELECT COUNT(*) as c FROM blobs WHERE sha256 = ?',
805
- ).get(sha256);
827
+ interface CountRow {
828
+ c: number;
829
+ }
830
+ const row = db.query<CountRow, [string]>('SELECT COUNT(*) as c FROM blobs WHERE sha256 = ?').get(sha256);
806
831
  return (row?.c ?? 0) > 0;
807
832
  }
808
833
 
@@ -828,10 +853,9 @@ function clampTimelineLimit(limit: number | undefined): number {
828
853
  }
829
854
 
830
855
  function trimAxTable(db: Database, table: 'ax_events' | 'ax_evidence' | 'ax_steering'): void {
831
- db.run(
832
- `DELETE FROM ${table} WHERE seq <= (SELECT seq FROM ${table} ORDER BY seq DESC LIMIT 1 OFFSET ?)`,
833
- [AX_TIMELINE_RETENTION],
834
- );
856
+ db.run(`DELETE FROM ${table} WHERE seq <= (SELECT seq FROM ${table} ORDER BY seq DESC LIMIT 1 OFFSET ?)`, [
857
+ AX_TIMELINE_RETENTION,
858
+ ]);
835
859
  }
836
860
 
837
861
  function readLastSeq(db: Database, table: 'ax_events' | 'ax_evidence' | 'ax_steering'): number {
@@ -842,7 +866,16 @@ function readLastSeq(db: Database, table: 'ax_events' | 'ax_evidence' | 'ax_stee
842
866
  export function appendAxEventToDB(db: Database, ev: Omit<PmxAxEvent, 'seq'>): PmxAxEvent {
843
867
  db.run(
844
868
  'INSERT INTO ax_events (id, kind, summary, detail, node_ids, data, created_at, source) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
845
- [ev.id, ev.kind, ev.summary, ev.detail, JSON.stringify(ev.nodeIds), ev.data ? JSON.stringify(ev.data) : null, ev.createdAt, ev.source],
869
+ [
870
+ ev.id,
871
+ ev.kind,
872
+ ev.summary,
873
+ ev.detail,
874
+ JSON.stringify(ev.nodeIds),
875
+ ev.data ? JSON.stringify(ev.data) : null,
876
+ ev.createdAt,
877
+ ev.source,
878
+ ],
846
879
  );
847
880
  const seq = readLastSeq(db, 'ax_events');
848
881
  trimAxTable(db, 'ax_events');
@@ -852,7 +885,17 @@ export function appendAxEventToDB(db: Database, ev: Omit<PmxAxEvent, 'seq'>): Pm
852
885
  export function appendAxEvidenceToDB(db: Database, ev: Omit<PmxAxEvidence, 'seq'>): PmxAxEvidence {
853
886
  db.run(
854
887
  'INSERT INTO ax_evidence (id, kind, title, body, ref, node_ids, data, created_at, source) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
855
- [ev.id, ev.kind, ev.title, ev.body, ev.ref, JSON.stringify(ev.nodeIds), ev.data ? JSON.stringify(ev.data) : null, ev.createdAt, ev.source],
888
+ [
889
+ ev.id,
890
+ ev.kind,
891
+ ev.title,
892
+ ev.body,
893
+ ev.ref,
894
+ JSON.stringify(ev.nodeIds),
895
+ ev.data ? JSON.stringify(ev.data) : null,
896
+ ev.createdAt,
897
+ ev.source,
898
+ ],
856
899
  );
857
900
  const seq = readLastSeq(db, 'ax_evidence');
858
901
  trimAxTable(db, 'ax_evidence');
@@ -860,10 +903,13 @@ export function appendAxEvidenceToDB(db: Database, ev: Omit<PmxAxEvidence, 'seq'
860
903
  }
861
904
 
862
905
  export function appendAxSteeringToDB(db: Database, s: Omit<PmxAxSteeringMessage, 'seq'>): PmxAxSteeringMessage {
863
- db.run(
864
- 'INSERT INTO ax_steering (id, message, delivered, created_at, source) VALUES (?, ?, ?, ?, ?)',
865
- [s.id, s.message, s.delivered ? 1 : 0, s.createdAt, s.source],
866
- );
906
+ db.run('INSERT INTO ax_steering (id, message, delivered, created_at, source) VALUES (?, ?, ?, ?, ?)', [
907
+ s.id,
908
+ s.message,
909
+ s.delivered ? 1 : 0,
910
+ s.createdAt,
911
+ s.source,
912
+ ]);
867
913
  const seq = readLastSeq(db, 'ax_steering');
868
914
  trimAxTable(db, 'ax_steering');
869
915
  return { ...s, seq };
@@ -875,23 +921,72 @@ export function markAxSteeringDeliveredInDB(db: Database, id: string): boolean {
875
921
  }
876
922
 
877
923
  export function loadAxEventsFromDB(db: Database, q: AxTimelineQuery = {}): PmxAxEvent[] {
878
- interface Row { seq: number; id: string; kind: string; summary: string; detail: string | null; node_ids: string; data: string | null; created_at: string; source: string | null }
879
- const rows = db.query<Row, [number]>('SELECT * FROM ax_events ORDER BY seq DESC LIMIT ?').all(clampTimelineLimit(q.limit));
924
+ interface Row {
925
+ seq: number;
926
+ id: string;
927
+ kind: string;
928
+ summary: string;
929
+ detail: string | null;
930
+ node_ids: string;
931
+ data: string | null;
932
+ created_at: string;
933
+ source: string | null;
934
+ }
935
+ const rows = db
936
+ .query<Row, [number]>('SELECT * FROM ax_events ORDER BY seq DESC LIMIT ?')
937
+ .all(clampTimelineLimit(q.limit));
880
938
  return rows
881
- .map((r) => normalizeAxEvent({ ...r, createdAt: r.created_at, nodeIds: safeParseJson(r.node_ids), data: safeParseJson(r.data) }))
939
+ .map((r) =>
940
+ normalizeAxEvent({
941
+ ...r,
942
+ createdAt: r.created_at,
943
+ nodeIds: safeParseJson(r.node_ids),
944
+ data: safeParseJson(r.data),
945
+ }),
946
+ )
882
947
  .filter((e): e is PmxAxEvent => e !== null);
883
948
  }
884
949
 
885
950
  export function loadAxEvidenceFromDB(db: Database, q: AxTimelineQuery = {}): PmxAxEvidence[] {
886
- interface Row { seq: number; id: string; kind: string; title: string; body: string | null; ref: string | null; node_ids: string; data: string | null; created_at: string; source: string | null }
887
- const rows = db.query<Row, [number]>('SELECT * FROM ax_evidence ORDER BY seq DESC LIMIT ?').all(clampTimelineLimit(q.limit));
951
+ interface Row {
952
+ seq: number;
953
+ id: string;
954
+ kind: string;
955
+ title: string;
956
+ body: string | null;
957
+ ref: string | null;
958
+ node_ids: string;
959
+ data: string | null;
960
+ created_at: string;
961
+ source: string | null;
962
+ }
963
+ const rows = db
964
+ .query<Row, [number]>('SELECT * FROM ax_evidence ORDER BY seq DESC LIMIT ?')
965
+ .all(clampTimelineLimit(q.limit));
888
966
  return rows
889
- .map((r) => normalizeAxEvidence({ ...r, createdAt: r.created_at, nodeIds: safeParseJson(r.node_ids), data: safeParseJson(r.data) }))
967
+ .map((r) =>
968
+ normalizeAxEvidence({
969
+ ...r,
970
+ createdAt: r.created_at,
971
+ nodeIds: safeParseJson(r.node_ids),
972
+ data: safeParseJson(r.data),
973
+ }),
974
+ )
890
975
  .filter((e): e is PmxAxEvidence => e !== null);
891
976
  }
892
977
 
893
- export function loadAxSteeringFromDB(db: Database, q: AxTimelineQuery & { onlyPending?: boolean } = {}): PmxAxSteeringMessage[] {
894
- interface Row { seq: number; id: string; message: string; delivered: number; created_at: string; source: string | null }
978
+ export function loadAxSteeringFromDB(
979
+ db: Database,
980
+ q: AxTimelineQuery & { onlyPending?: boolean } = {},
981
+ ): PmxAxSteeringMessage[] {
982
+ interface Row {
983
+ seq: number;
984
+ id: string;
985
+ message: string;
986
+ delivered: number;
987
+ created_at: string;
988
+ source: string | null;
989
+ }
895
990
  const sql = q.onlyPending
896
991
  ? 'SELECT * FROM ax_steering WHERE delivered = 0 ORDER BY seq DESC LIMIT ?'
897
992
  : 'SELECT * FROM ax_steering ORDER BY seq DESC LIMIT ?';
@@ -905,17 +1000,24 @@ export function loadPendingAxSteeringFromDB(
905
1000
  db: Database,
906
1001
  options: { consumer?: string; limit?: number } = {},
907
1002
  ): PmxAxSteeringMessage[] {
908
- interface Row { seq: number; id: string; message: string; delivered: number; created_at: string; source: string | null }
1003
+ interface Row {
1004
+ seq: number;
1005
+ id: string;
1006
+ message: string;
1007
+ delivered: number;
1008
+ created_at: string;
1009
+ source: string | null;
1010
+ }
909
1011
  // FIFO (oldest undelivered first); exclude the consumer's own steering in SQL
910
1012
  // so the LIMIT is applied AFTER loop-prevention, not before.
911
1013
  const limit = clampTimelineLimit(options.limit);
912
1014
  const rows = options.consumer
913
- ? db.query<Row, [string, number]>(
914
- 'SELECT * FROM ax_steering WHERE delivered = 0 AND (source IS NULL OR source != ?) ORDER BY seq ASC LIMIT ?',
915
- ).all(options.consumer, limit)
916
- : db.query<Row, [number]>(
917
- 'SELECT * FROM ax_steering WHERE delivered = 0 ORDER BY seq ASC LIMIT ?',
918
- ).all(limit);
1015
+ ? db
1016
+ .query<Row, [string, number]>(
1017
+ 'SELECT * FROM ax_steering WHERE delivered = 0 AND (source IS NULL OR source != ?) ORDER BY seq ASC LIMIT ?',
1018
+ )
1019
+ .all(options.consumer, limit)
1020
+ : db.query<Row, [number]>('SELECT * FROM ax_steering WHERE delivered = 0 ORDER BY seq ASC LIMIT ?').all(limit);
919
1021
  return rows
920
1022
  .map((r) => normalizeAxSteeringMessage({ ...r, createdAt: r.created_at, delivered: r.delivered === 1 }))
921
1023
  .filter((s): s is PmxAxSteeringMessage => s !== null);
@@ -932,15 +1034,22 @@ export function loadNewestPendingAxSteeringFromDB(
932
1034
  db: Database,
933
1035
  options: { consumer?: string; limit?: number } = {},
934
1036
  ): PmxAxSteeringMessage[] {
935
- interface Row { seq: number; id: string; message: string; delivered: number; created_at: string; source: string | null }
1037
+ interface Row {
1038
+ seq: number;
1039
+ id: string;
1040
+ message: string;
1041
+ delivered: number;
1042
+ created_at: string;
1043
+ source: string | null;
1044
+ }
936
1045
  const limit = clampTimelineLimit(options.limit);
937
1046
  const rows = options.consumer
938
- ? db.query<Row, [string, number]>(
939
- 'SELECT * FROM ax_steering WHERE delivered = 0 AND (source IS NULL OR source != ?) ORDER BY seq DESC LIMIT ?',
940
- ).all(options.consumer, limit)
941
- : db.query<Row, [number]>(
942
- 'SELECT * FROM ax_steering WHERE delivered = 0 ORDER BY seq DESC LIMIT ?',
943
- ).all(limit);
1047
+ ? db
1048
+ .query<Row, [string, number]>(
1049
+ 'SELECT * FROM ax_steering WHERE delivered = 0 AND (source IS NULL OR source != ?) ORDER BY seq DESC LIMIT ?',
1050
+ )
1051
+ .all(options.consumer, limit)
1052
+ : db.query<Row, [number]>('SELECT * FROM ax_steering WHERE delivered = 0 ORDER BY seq DESC LIMIT ?').all(limit);
944
1053
  return rows
945
1054
  .map((r) => normalizeAxSteeringMessage({ ...r, createdAt: r.created_at, delivered: r.delivered === 1 }))
946
1055
  .filter((s): s is PmxAxSteeringMessage => s !== null);
@@ -949,9 +1058,11 @@ export function loadNewestPendingAxSteeringFromDB(
949
1058
  /** Total undelivered steering for a consumer (loop-safe — excludes the consumer's own). */
950
1059
  export function countPendingAxSteeringFromDB(db: Database, consumer?: string): number {
951
1060
  const n = consumer
952
- ? db.query<{ n: number }, [string]>(
953
- 'SELECT COUNT(*) AS n FROM ax_steering WHERE delivered = 0 AND (source IS NULL OR source != ?)',
954
- ).get(consumer)?.n
1061
+ ? db
1062
+ .query<{ n: number }, [string]>(
1063
+ 'SELECT COUNT(*) AS n FROM ax_steering WHERE delivered = 0 AND (source IS NULL OR source != ?)',
1064
+ )
1065
+ .get(consumer)?.n
955
1066
  : db.query<{ n: number }, []>('SELECT COUNT(*) AS n FROM ax_steering WHERE delivered = 0').get()?.n;
956
1067
  return Number(n ?? 0);
957
1068
  }
@@ -965,19 +1076,26 @@ export function loadAxTimelineSummaryFromDB(db: Database): PmxAxTimelineSummary
965
1076
  recentEvents: loadAxEventsFromDB(db, { limit: AX_CONTEXT_EVENT_LIMIT }),
966
1077
  recentEvidence: loadAxEvidenceFromDB(db, { limit: AX_CONTEXT_EVIDENCE_LIMIT }),
967
1078
  pendingSteering: loadAxSteeringFromDB(db, { onlyPending: true, limit: AX_CONTEXT_STEERING_LIMIT }),
968
- counts: { events: countRows(db, 'ax_events'), evidence: countRows(db, 'ax_evidence'), steering: countRows(db, 'ax_steering') },
1079
+ counts: {
1080
+ events: countRows(db, 'ax_events'),
1081
+ evidence: countRows(db, 'ax_evidence'),
1082
+ steering: countRows(db, 'ax_steering'),
1083
+ },
969
1084
  };
970
1085
  }
971
1086
 
972
1087
  export function upsertAxHostCapabilityToDB(db: Database, cap: PmxAxHostCapability): void {
973
1088
  const host = cap.host ?? 'default';
974
- db.run(
975
- 'INSERT OR REPLACE INTO ax_host_capabilities (host, reported_at, payload) VALUES (?, ?, ?)',
976
- [host, cap.reportedAt ?? new Date().toISOString(), JSON.stringify(cap)],
977
- );
1089
+ db.run('INSERT OR REPLACE INTO ax_host_capabilities (host, reported_at, payload) VALUES (?, ?, ?)', [
1090
+ host,
1091
+ cap.reportedAt ?? new Date().toISOString(),
1092
+ JSON.stringify(cap),
1093
+ ]);
978
1094
  }
979
1095
 
980
1096
  export function loadAxHostCapabilityFromDB(db: Database): PmxAxHostCapability | null {
981
- const row = db.query<{ payload: string }, []>('SELECT payload FROM ax_host_capabilities ORDER BY reported_at DESC LIMIT 1').get();
1097
+ const row = db
1098
+ .query<{ payload: string }, []>('SELECT payload FROM ax_host_capabilities ORDER BY reported_at DESC LIMIT 1')
1099
+ .get();
982
1100
  return row ? normalizeAxHostCapability(safeParseJson(row.payload)) : null;
983
1101
  }