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.
- package/CHANGELOG.md +38 -0
- package/dist/canvas/index.js +2 -2
- package/dist/types/cli/daemon.d.ts +74 -0
- package/dist/types/cli/watch.d.ts +2 -2
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
- package/dist/types/client/canvas/Minimap.d.ts +1 -1
- package/dist/types/client/nodes/FileNode.d.ts +1 -1
- package/dist/types/client/nodes/ImageNode.d.ts +1 -1
- package/dist/types/client/nodes/InlineFormatBar.d.ts +1 -1
- package/dist/types/client/nodes/MarkdownNode.d.ts +1 -1
- package/dist/types/client/nodes/PromptNode.d.ts +1 -1
- package/dist/types/client/nodes/ResponseNode.d.ts +1 -1
- package/dist/types/server/canvas-schema.d.ts +1 -1
- package/dist/types/server/html-primitives.d.ts +1 -1
- package/dist/types/server/index.d.ts +4 -4
- package/dist/types/server/operations/index.d.ts +1 -1
- package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
- package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
- package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
- package/docs/http-api.md +28 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
- package/docs/tech-debt-assessment-2026-07.md +2 -2
- package/package.json +5 -2
- package/skills/pmx-canvas/SKILL.md +3 -1
- package/skills/pmx-canvas/references/full-reference.md +10 -3
- package/src/cli/agent.ts +1861 -1548
- package/src/cli/daemon.ts +460 -0
- package/src/cli/index.ts +63 -326
- package/src/cli/watch.ts +2 -10
- package/src/client/App.tsx +48 -46
- package/src/client/canvas/AttentionHistory.tsx +11 -1
- package/src/client/canvas/CanvasNode.tsx +41 -29
- package/src/client/canvas/CanvasViewport.tsx +101 -66
- package/src/client/canvas/CommandPalette.tsx +61 -27
- package/src/client/canvas/ContextMenu.tsx +13 -20
- package/src/client/canvas/ContextPinBar.tsx +1 -5
- package/src/client/canvas/ContextPinHud.tsx +1 -6
- package/src/client/canvas/DockedNode.tsx +4 -4
- package/src/client/canvas/EdgeLayer.tsx +37 -36
- package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
- package/src/client/canvas/FocusFieldLayer.tsx +20 -22
- package/src/client/canvas/IntentLayer.tsx +31 -14
- package/src/client/canvas/Minimap.tsx +11 -16
- package/src/client/canvas/SelectionBar.tsx +4 -11
- package/src/client/canvas/ShortcutOverlay.tsx +3 -1
- package/src/client/canvas/SnapshotPanel.tsx +77 -95
- package/src/client/canvas/auto-fit.ts +15 -14
- package/src/client/canvas/snap-guides.ts +12 -12
- package/src/client/canvas/use-node-resize.ts +1 -5
- package/src/client/canvas/use-pan-zoom.ts +25 -26
- package/src/client/ext-app/bridge.ts +3 -12
- package/src/client/icons.tsx +63 -20
- package/src/client/nodes/ContextNode.tsx +14 -25
- package/src/client/nodes/ExtAppFrame.tsx +60 -39
- package/src/client/nodes/FileNode.tsx +74 -62
- package/src/client/nodes/GroupNode.tsx +4 -6
- package/src/client/nodes/HtmlNode.tsx +76 -46
- package/src/client/nodes/ImageNode.tsx +18 -27
- package/src/client/nodes/InlineFormatBar.tsx +4 -21
- package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
- package/src/client/nodes/LedgerNode.tsx +10 -4
- package/src/client/nodes/MarkdownNode.tsx +3 -10
- package/src/client/nodes/McpAppNode.tsx +26 -22
- package/src/client/nodes/MdFormatBar.tsx +10 -7
- package/src/client/nodes/PromptNode.tsx +23 -51
- package/src/client/nodes/ResponseNode.tsx +3 -13
- package/src/client/nodes/StatusNode.tsx +5 -9
- package/src/client/nodes/StatusSummary.tsx +2 -8
- package/src/client/nodes/WebpageNode.tsx +20 -14
- package/src/client/nodes/iframe-document-url.ts +25 -16
- package/src/client/nodes/image-warnings.ts +1 -7
- package/src/client/nodes/md-format.ts +20 -5
- package/src/client/state/attention-bridge.ts +4 -9
- package/src/client/state/attention-store.ts +1 -7
- package/src/client/state/canvas-store.ts +52 -36
- package/src/client/state/intent-bridge.ts +176 -112
- package/src/client/state/intent-store.ts +4 -1
- package/src/client/state/sse-bridge.ts +53 -70
- package/src/json-render/catalog.ts +12 -16
- package/src/json-render/charts/components.tsx +16 -20
- package/src/json-render/charts/extra-components.tsx +8 -16
- package/src/json-render/charts/extra-definitions.ts +1 -2
- package/src/json-render/charts/tufte-components.tsx +37 -20
- package/src/json-render/charts/tufte-definitions.ts +8 -2
- package/src/json-render/renderer/index.tsx +42 -22
- package/src/json-render/schema.ts +6 -3
- package/src/json-render/server.ts +33 -39
- package/src/mcp/canvas-access.ts +35 -21
- package/src/mcp/server.ts +132 -70
- package/src/server/agent-context.ts +63 -36
- package/src/server/ax-context.ts +7 -5
- package/src/server/ax-interaction.ts +176 -43
- package/src/server/ax-state-manager.ts +182 -39
- package/src/server/ax-state.ts +142 -47
- package/src/server/canvas-db.ts +213 -95
- package/src/server/canvas-operations.ts +177 -120
- package/src/server/canvas-provenance.ts +1 -4
- package/src/server/canvas-schema.ts +454 -73
- package/src/server/canvas-serialization.ts +27 -35
- package/src/server/canvas-state.ts +150 -58
- package/src/server/chart-template.ts +4 -5
- package/src/server/code-graph.ts +19 -6
- package/src/server/diagram-presets.ts +28 -29
- package/src/server/ext-app-lookup.ts +3 -12
- package/src/server/html-node-summary.ts +19 -10
- package/src/server/html-primitives.ts +326 -97
- package/src/server/html-surface.ts +6 -9
- package/src/server/image-source.ts +6 -4
- package/src/server/index.ts +320 -217
- package/src/server/intent-registry.ts +2 -5
- package/src/server/mcp-app-candidate.ts +5 -10
- package/src/server/mcp-app-host.ts +14 -38
- package/src/server/mcp-app-runtime.ts +12 -20
- package/src/server/mutation-history.ts +15 -5
- package/src/server/operations/composites.ts +1 -3
- package/src/server/operations/http.ts +2 -3
- package/src/server/operations/index.ts +7 -1
- package/src/server/operations/invoker.ts +4 -3
- package/src/server/operations/mcp.ts +22 -30
- package/src/server/operations/ops/annotation.ts +122 -10
- package/src/server/operations/ops/app.ts +98 -73
- package/src/server/operations/ops/ax-await.ts +17 -10
- package/src/server/operations/ops/ax-read.ts +347 -0
- package/src/server/operations/ops/ax-shared.ts +2 -7
- package/src/server/operations/ops/ax-state.ts +32 -14
- package/src/server/operations/ops/ax-timeline.ts +32 -19
- package/src/server/operations/ops/ax-work.ts +54 -37
- package/src/server/operations/ops/batch.ts +39 -14
- package/src/server/operations/ops/canvas-wire.ts +91 -0
- package/src/server/operations/ops/edges.ts +37 -25
- package/src/server/operations/ops/ext-app.ts +346 -0
- package/src/server/operations/ops/groups.ts +49 -20
- package/src/server/operations/ops/intent.ts +18 -12
- package/src/server/operations/ops/json-render.ts +239 -98
- package/src/server/operations/ops/nodes.ts +298 -109
- package/src/server/operations/ops/query.ts +46 -28
- package/src/server/operations/ops/snapshots.ts +35 -26
- package/src/server/operations/ops/validate.ts +2 -1
- package/src/server/operations/ops/viewport.ts +60 -16
- package/src/server/operations/ops/webview.ts +44 -18
- package/src/server/operations/registry.ts +2 -3
- package/src/server/operations/types.ts +7 -5
- package/src/server/operations/webview-runner.ts +1 -3
- package/src/server/placement.ts +8 -18
- package/src/server/server.ts +108 -1027
- package/src/server/spatial-analysis.ts +39 -25
- package/src/server/trace-manager.ts +3 -8
- package/src/server/web-artifacts.ts +23 -27
- package/src/server/webpage-node.ts +5 -13
- package/src/shared/auto-arrange.ts +12 -5
- package/src/shared/content-height-reporter.ts +8 -6
- package/src/shared/ext-app-tool-result.ts +2 -6
- package/src/shared/placement.ts +1 -4
- package/src/shared/semantic-attention.ts +39 -37
- package/src/shared/surface.ts +8 -4
package/src/server/canvas-db.ts
CHANGED
|
@@ -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 (?, ?)', [
|
|
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
|
|
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 {
|
|
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
|
-
|
|
496
|
-
|
|
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 (?, ?, ?)', [
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
|
593
|
-
'SELECT * FROM snapshots WHERE name = ? ORDER BY created_at DESC LIMIT 1'
|
|
594
|
-
|
|
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 {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
|
635
|
-
'SELECT * FROM snapshot_nodes WHERE snapshot_id = ?'
|
|
636
|
-
|
|
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
|
|
660
|
-
'SELECT * FROM snapshot_edges WHERE snapshot_id = ?'
|
|
661
|
-
|
|
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
|
|
685
|
-
'SELECT * FROM snapshot_annotations WHERE snapshot_id = ?'
|
|
686
|
-
|
|
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 {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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
|
-
|
|
787
|
-
|
|
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 {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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 {
|
|
803
|
-
|
|
804
|
-
|
|
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
|
-
|
|
833
|
-
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
|
|
865
|
-
|
|
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 {
|
|
879
|
-
|
|
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) =>
|
|
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 {
|
|
887
|
-
|
|
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) =>
|
|
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(
|
|
894
|
-
|
|
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 {
|
|
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
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
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 {
|
|
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
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
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
|
|
953
|
-
|
|
954
|
-
|
|
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: {
|
|
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
|
-
|
|
976
|
-
|
|
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
|
|
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
|
}
|